Understanding Lock Strategies: Optimistic, Pessimistic, Read‑Write, Heavy/Light, Fairness and Reentrancy
This article explains various lock strategies—including optimistic and pessimistic locks, read‑write locks, heavyweight vs. lightweight locks, spin locks, fair vs. unfair locks, and reentrant locks—detailing their definitions, use‑cases, underlying mechanisms, and illustrative examples for concurrent programming in Java and other languages.
1. Optimistic and Pessimistic Locks
Both lock types have specific scenarios where they are appropriate.
1.1 Definition
Optimistic lock: Assumes no conflict during reads/writes; threads are not blocked. Conflict is checked only when updating data, and if a conflict occurs (multiple threads updating), it is resolved.
When contention is low, optimistic locking avoids repeated lock/unlock overhead.
Pessimistic lock: Assumes conflicts on every read/write; each access acquires an exclusive lock, ensuring only one thread accesses the data at a time. This is used when contention is high to prevent CPU waste from busy‑waiting.
1.2 Illustrative Example
For a pessimistic lock, a thread must request a lock before interacting with a resource and wait until it is granted; for an optimistic lock, the thread proceeds without locking and only retries if a version check fails.
1.3 Version Number Mechanism
Optimistic locks often use a version number to detect conflicts.
Most implementations combine optimistic and pessimistic strategies.
synchronized starts as optimistic and upgrades to pessimistic under high contention.
Typical workflow:
Threads read data and its version (e.g., version = 1).
Each thread modifies its local copy.
Thread 1 commits first, increments version to 2, and writes back.
Thread 2 then attempts to commit; seeing the version has changed, it aborts and retries.
2. Read‑Write Locks
2.1 Origin
Multiple readers can safely read concurrently, but writers need exclusive access; using a single lock for both leads to performance loss, so read‑write locks were introduced.
Read‑write locks are ideal when reads dominate writes.
Multiple threads can hold the read lock simultaneously (no mutual exclusion).
Only one thread can hold the write lock; others block.
If one thread reads while another writes, they are mutually exclusive.
2.2 Example
Imagine a writer (author) and many readers; readers must wait until the author finishes writing before they can read.
2.3 ReentrantReadWriteLock Class (Java)
The JDK provides ReentrantReadWriteLock for implementing read‑write locks.
ReentrantReadWriteLock.ReadLock represents a read lock with lock() and unlock() methods.
ReentrantReadWriteLock.WriteLock represents a write lock with the same methods.
3. Heavyweight vs. Lightweight Locks
Lock atomicity relies on CPU‑provided atomic instructions.
CPU offers atomic operations.
The OS builds mutexes on top of them.
The JVM builds synchronized and ReentrantLock on OS mutexes.
3.1 Definition
Heavyweight lock: Requires OS and hardware support; thread blocking involves a transition to kernel mode, incurring high overhead.
Lightweight lock: Operates mostly in user mode; threads do not block, avoiding costly context switches.
3.2 Example
Banking analogy: operations handled entirely by the teller (user mode) are lightweight, while those needing manager approval (kernel mode) are heavyweight.
3.3 Spin Lock
When a thread fails to acquire a lock, instead of blocking, it repeatedly checks (spins) until the lock becomes available, keeping the CPU busy.
Typical pseudo‑code:
while (acquire(lock) == false) { // spin }Spin locks are a common implementation of lightweight locks.
4. Fair vs. Unfair Locks
Fair lock: Threads acquire the lock in FIFO order; the longest‑waiting thread gets the lock first.
Unfair lock: Threads compete for the lock without strict ordering; any waiting thread may acquire it.
synchronized is an unfair lock.
ReentrantLock is unfair by default but can be made fair via its constructor.
5. Reentrant vs. Non‑Reentrant Locks
Reentrant locks allow the same thread to acquire the same lock multiple times.
Example: a recursive method that locks a resource can re‑enter without deadlocking.
In Java, any lock whose name starts with "Reentrant" (including synchronized ) is reentrant; Linux mutexes are non‑reentrant.
---
Source: blog.csdn.net/qq_43575801/article/details/127760429
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.