Why Java Still Needs Lock: Beyond synchronized with Non‑Blocking, Fair, and Read‑Write Features
This article explains why Java’s Lock interface is still needed despite synchronized, covering non‑blocking tryLock, timed lock acquisition, interruptible locking, fair versus non‑fair locks, read‑write locks, and Condition objects, with code samples and execution logs illustrating each feature.
In early JDK versions (1.5) the synchronized keyword incurred heavy locking overhead, often slower than the business logic itself. Since JDK 1.6 the JVM has optimized synchronized with lock elimination, biased locking, lightweight locks, etc., making its performance comparable to explicit locks and recommending its use for simple cases.
Nevertheless, the Lock interface provides capabilities that synchronized cannot offer, such as non‑blocking acquisition, timed waiting, interruptible lock acquisition, fairness control, read‑write separation, and multiple condition queues.
Non‑Blocking Lock Acquisition
Using boolean tryLock() attempts to obtain the lock immediately and returns true on success or false without blocking. Example:
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final ReentrantLock lock = new ReentrantLock();
public void execute() {
if (lock.tryLock()) {
try {
System.out.println("Lock acquired, work performed here.");
} finally {
lock.unlock();
}
} else {
System.out.println("Could not acquire lock, work performed here.");
}
}
public static void main(String[] args) {
new LockExample().execute();
}
}Timed Lock Acquisition
The method boolean tryLock(long time, TimeUnit unit) waits up to the specified time for the lock. Example:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final ReentrantLock lock = new ReentrantLock();
public void execute() {
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
System.out.println("Lock acquired, work performed here.");
} finally {
lock.unlock();
}
} else {
System.out.println("Could not acquire lock, work performed here.");
}
}
public static void main(String[] args) throws Exception {
new LockExample().execute();
}
}Interruptible Lock Acquisition
void lockInterruptibly()allows a thread waiting for the lock to be interrupted. The following demo shows an IllegalMonitorStateException caused by unlocking without owning the lock:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLockInterruptibly {
final Lock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
TestLockInterruptibly t = new TestLockInterruptibly();
Thread lockThread = new Thread(() -> t.lock());
Thread interruptiblyThread = new Thread(() -> t.lockInterruptibly());
lockThread.start();
interruptiblyThread.start();
TimeUnit.SECONDS.sleep(2);
interruptiblyThread.interrupt();
}
public void lockInterruptibly() {
try {
TimeUnit.SECONDS.sleep(1);
lock.lockInterruptibly();
System.out.println("Lock acquired via lockInterruptibly");
} catch (InterruptedException e) {
System.out.println("Caught InterruptedException");
} finally {
lock.unlock();
System.out.println("Lock released via lockInterruptibly");
}
}
public void lock() {
try {
lock.lock();
System.out.println("Lock acquired via lock");
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
System.out.println("Caught InterruptedException");
} finally {
lock.unlock();
System.out.println("Lock released via lock");
}
}
}Output demonstrates the exception when the lock is released incorrectly.
Fair vs. Non‑Fair Locks
ReentrantLockcan be constructed as fair ( new ReentrantLock(true)) or non‑fair. Fair locks maintain a FIFO queue, incurring higher overhead and lower throughput, but they prevent thread starvation in high‑contention scenarios.
Fair lock example:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private static final ReentrantLock fairLock = new ReentrantLock(true);
public static void main(String[] args) {
Runnable task = () -> {
while (true) {
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + " acquires lock");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " releases lock");
fairLock.unlock();
}
}
};
new Thread(task, "Thread-1").start();
new Thread(task, "Thread-2").start();
new Thread(task, "Thread-3").start();
}
}Sample log shows each thread acquiring the lock in order.
Non‑fair lock example (default constructor) exhibits higher throughput but no ordering guarantee:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class NonFairLockExample {
private static final ReentrantLock lock = new ReentrantLock();
// similar task as above without fairness flag
}Read‑Write Locks
ReentrantReadWriteLockallows multiple concurrent readers while ensuring exclusive access for writers. This improves throughput when reads dominate writes.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
public void readData() {
try {
readLock.lock();
System.out.println(Thread.currentThread().getName() + " acquires read lock");
TimeUnit.SECONDS.sleep(1);
} finally {
System.out.println(Thread.currentThread().getName() + " releases read lock");
readLock.unlock();
}
}
public void writeData() {
try {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + " acquires write lock");
TimeUnit.SECONDS.sleep(1);
} finally {
System.out.println(Thread.currentThread().getName() + " releases write lock");
writeLock.unlock();
}
}
public static void main(String[] args) {
ReadWriteLockExample ex = new ReadWriteLockExample();
// launch several reader threads
for (int i = 0; i < 10; i++) {
new Thread(ex::readData).start();
}
// one writer thread
new Thread(ex::writeData).start();
}
}Logs show concurrent read lock acquisition and exclusive write lock behavior.
Condition Objects
Each Lock can create multiple Condition instances, offering finer‑grained thread notification than the single monitor associated with synchronized. The following demo creates two conditions and signals them independently.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition conditionA = lock.newCondition();
private final Condition conditionB = lock.newCondition();
public void awaitA() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " awaiting A");
conditionA.await();
System.out.println(Thread.currentThread().getName() + " awakened A");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " awaiting B");
conditionB.await();
System.out.println(Thread.currentThread().getName() + " awakened B");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signalA() {
lock.lock();
try {
System.out.println("Signalling A");
conditionA.signalAll();
} finally {
lock.unlock();
}
}
public void signalB() {
lock.lock();
try {
System.out.println("Signalling B");
conditionB.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionExample ce = new ConditionExample();
new Thread(ce::awaitA).start();
new Thread(ce::awaitB).start();
TimeUnit.SECONDS.sleep(1);
ce.signalA();
ce.signalB();
}
}Output demonstrates that each waiting thread is resumed by its corresponding signal.
Conclusion
The existence of the Lock API is justified because it supplies non‑blocking acquisition, timed waits, interruptibility, fairness control, read‑write separation, and multiple condition queues—features that synchronized alone cannot provide.
Senior Tony
Former senior tech manager at Meituan, ex‑tech director at New Oriental, with experience at JD.com and Qunar; specializes in Java interview coaching and regularly shares hardcore technical content. Runs a video channel of the same name.
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.
