Understanding Java Locks: Optimistic vs Pessimistic, Reentrant, Read‑Write, and More
This article explains the various Java lock mechanisms—including optimistic and pessimistic locks, exclusive and shared locks, reentrant, read‑write, fair/unfair, spin, segment locks, and lock‑escalation techniques—detailing their principles, Java implementations, usage scenarios, and optimization strategies.
Optimistic Lock and Pessimistic Lock
Pessimistic Lock
In code, a pessimistic lock assumes that other threads may modify the shared data, so each operation acquires the lock, causing other threads to block.
In Java, synchronized and ReentrantLock are typical pessimistic locks; container classes such as Hashtable also use them.
Optimistic Lock
An optimistic lock does not lock during normal operations; it checks for conflicts only when updating the data.
Java implements optimistic locking via version numbers and the CAS algorithm, e.g., the atomic classes in java.util.concurrent.atomic.
When to Use Each
Optimistic locks suit scenarios with few writes (low contention) because they avoid lock overhead, improving throughput. In write‑heavy, read‑light scenarios with high contention, pessimistic locks are more appropriate.
Exclusive Lock and Shared Lock
Exclusive Lock
An exclusive lock can be held by only one thread at a time; the holder can both read and modify the data.
In the JDK, synchronized and the Lock implementations in java.util.concurrent are exclusive locks.
Shared Lock
A shared lock can be held by multiple threads simultaneously, but only for reading; it cannot be upgraded to an exclusive lock.
The JDK class ReentrantReadWriteLock provides a shared (read) lock.
Mutex Lock and Read‑Write Lock
Mutex Lock
A mutex is a conventional exclusive lock that allows only one thread to access the protected resource at a time.
Read‑Write Lock
A read‑write lock separates read and write access: multiple threads may hold the read lock concurrently, while the write lock is exclusive and has higher priority.
JDK defines the ReadWriteLock interface:
public interface ReadWriteLock {
/** Get the read lock */
Lock readLock();
/** Get the write lock */
Lock writeLock();
} ReentrantReadWriteLockimplements this interface.
Fair Lock and Unfair Lock
Fair Lock
A fair lock grants access to threads in the order they request it, similar to a queue.
/** Create a reentrant lock; true = fair lock, false = unfair lock (default) */
Lock lock = new ReentrantLock(true);Unfair Lock
An unfair lock does not guarantee ordering; later threads may acquire the lock before earlier ones, which can cause priority inversion or starvation under high contention.
/** Create a reentrant lock; true = fair lock, false = unfair lock (default) */
Lock lock = new ReentrantLock(false);Reentrant Lock
A reentrant (or recursive) lock allows the same thread to acquire the lock multiple times without deadlocking.
Both ReentrantLock and synchronized are reentrant.
public synchronized void methodA() throws Exception {
// Do some work
methodB();
}
public synchronized void methodB() throws Exception {
// Do some work
}Spin Lock
A spin lock makes a thread repeatedly loop (spin) while waiting for the lock, reducing the overhead of thread suspension.
In Java, AtomicInteger uses a spin‑CAS loop:
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}Segment Lock
A segment lock is a design that narrows lock granularity by locking only a portion of a data structure, such as a single bucket in a hash table.
Java's ConcurrentHashMap uses segment locks internally.
Lock Escalation (No‑Lock, Biased, Lightweight, Heavyweight)
Since JDK 1.6, the JVM can upgrade a lock through four states depending on contention: no‑lock (optimistic), biased lock, lightweight lock, and heavyweight lock.
No‑Lock corresponds to optimistic locking.
Biased Lock favors the first thread that acquires the lock when there is no contention.
Lightweight Lock uses spinning when contention is low.
Heavyweight Lock blocks other threads when contention becomes high; it is effectively a mutex.
Lock Optimization Techniques
Lock Coarsening
Combines multiple synchronized blocks into a larger one to reduce lock acquisition overhead.
private static final Object LOCK = new Object();
for (int i = 0; i < 100; i++) {
synchronized (LOCK) {
// do some work
}
}
// After lock coarsening
synchronized (LOCK) {
for (int i = 0; i < 100; i++) {
// do some work
}
}Lock Elimination
The JVM can remove locks that are proven to be uncontended at runtime.
public String test(String s1, String s2) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(s1);
stringBuffer.append(s2);
return stringBuffer.toString();
}Although StringBuffer is synchronized, the method is thread‑safe without the lock, so the JVM eliminates it.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
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.
