Understanding Java Concurrency Locks: synchronized, ReentrantLock, ReadWriteLock, and StampedLock
This article explains Java 8's built‑in lock mechanisms—including synchronized, ReentrantLock, ReadWriteLock, and StampedLock—detailing their characteristics, advanced features, and practical usage through inventory, banking, and order‑processing examples with complete code demonstrations.
Introduction
Java 8 provides a rich set of lock mechanisms to support multithreaded concurrent programming, helping developers ensure thread safety and coordinate thread interactions.
1. Built‑in Lock (synchronized)
1.1 Synchronized Methods
public class SynchronizedExample {
private int count = 0;
// synchronized instance method
public synchronized void increment() {
count++;
}
// synchronized static method (locks the class object)
public static synchronized void staticMethod() {
// ...
}
}1.2 Synchronized Code Blocks
public class SynchronizedBlockExample {
private final Object lock = new Object();
private int count = 0;
public void increment() {
// lock the explicit object
synchronized(lock) {
count++;
}
}
}Characteristics
Mutual exclusion – only one thread can hold the lock at a time.
Re‑entrant – the same thread can acquire the lock repeatedly.
Non‑fair – acquisition order is not guaranteed.
Does not support interruption or timeout while waiting.
2. ReentrantLock
2.1 Basic Usage
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // acquire lock
try {
count++;
} finally {
lock.unlock(); // release lock
}
}
}2.2 Advanced Features
public class AdvancedReentrantLockExample {
// fair lock
private final ReentrantLock lock = new ReentrantLock(true);
public void tryLockExample() throws InterruptedException {
// wait up to 1 second to acquire the lock
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// critical section
} finally {
lock.unlock();
}
} else {
// handle lock acquisition failure
}
}
public void lockInterruptiblyExample() throws InterruptedException {
lock.lockInterruptibly(); // interruptible acquisition
try {
// critical section
} finally {
lock.unlock();
}
}
}Key capabilities:
Re‑entrant, same as synchronized.
Fairness can be configured via the constructor.
Supports tryLock with timeout, lockInterruptibly, and state queries such as isLocked(), isHeldByCurrentThread(), getQueueLength().
2.3 Business Scenario – High‑Concurrency Inventory Management
In a flash‑sale, multiple users attempt to purchase the same product simultaneously. The system must guarantee atomic inventory deduction, prevent overselling, maintain high throughput, and roll back correctly on failure.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* Inventory service using ReentrantLock for thread safety
*/
public class InventoryService {
private final Map<Long, Integer> inventoryMap = new HashMap<>();
// fine‑grained lock per product ID
private final Map<Long, ReentrantLock> lockMap = new ConcurrentHashMap<>();
public void initInventory(Long itemId, int quantity) {
inventoryMap.put(itemId, quantity);
lockMap.putIfAbsent(itemId, new ReentrantLock(true)); // fair lock
}
public boolean deductInventory(Long itemId, int quantity) {
ReentrantLock lock = lockMap.get(itemId);
if (lock == null) {
throw new IllegalArgumentException("Item not found");
}
try {
// try to acquire lock within 500 ms to avoid indefinite waiting
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
Integer current = inventoryMap.get(itemId);
if (current == null || current < quantity) {
return false; // insufficient stock
}
Thread.sleep(10); // simulate business processing
inventoryMap.put(itemId, current - quantity);
return true;
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " lock timeout");
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().getName() + " interrupted");
return false;
}
}
public int getInventory(Long itemId) {
return inventoryMap.getOrDefault(itemId, 0);
}
}A stress test creates 100 threads that simultaneously attempt to deduct one unit from an initial stock of 500, demonstrating correct final inventory and lock behavior.
3. ReadWriteLock
3.1 Basic Usage
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private int value;
public int getValue() {
rwLock.readLock().lock();
try {
return value;
} finally {
rwLock.readLock().unlock();
}
}
public void setValue(int newValue) {
rwLock.writeLock().lock();
try {
value = newValue;
} finally {
rwLock.writeLock().unlock();
}
}
}Characteristics:
Read lock is shared; multiple threads can hold it concurrently.
Write lock is exclusive; only one thread can hold it.
Read‑write mutual exclusion – a write lock blocks both reads and writes.
Both locks are re‑entrant; lock downgrade (write → read) is allowed, upgrade (read → write) is not.
3.2 Business Scenario – Bank Account Management
High‑frequency balance queries (≈100:1 read/write ratio) require fast, consistent reads, while transfers need strict synchronization.
Using ReentrantReadWriteLock per account allows concurrent reads and exclusive writes, avoiding the serialization caused by synchronized or plain ReentrantLock.
import java.util.concurrent.locks.*;
import java.math.BigDecimal;
import java.util.concurrent.*;
public class AccountService {
private final Map<String, Account> accountStore = new ConcurrentHashMap<>();
private final Map<String, ReadWriteLock> lockStore = new ConcurrentHashMap<>();
private static class Account {
String accountId;
BigDecimal balance;
}
public void initAccount(String accountId, BigDecimal initBalance) {
Account account = new Account();
account.accountId = accountId;
account.balance = initBalance;
accountStore.put(accountId, account);
lockStore.putIfAbsent(accountId, new ReentrantReadWriteLock(true)); // fair lock
}
public BigDecimal queryBalance(String accountId) {
ReadWriteLock lock = lockStore.get(accountId);
if (lock == null) throw new IllegalArgumentException("Account not found");
lock.readLock().lock();
try {
Account acc = accountStore.get(accountId);
Thread.sleep(5); // simulate I/O latency
return acc.balance;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Query interrupted");
} finally {
lock.readLock().unlock();
}
}
public boolean transfer(String from, String to, BigDecimal amount) {
// lock ordering by account ID to prevent deadlock
String first = from.compareTo(to) < 0 ? from : to;
String second = first.equals(from) ? to : from;
ReadWriteLock lock1 = lockStore.get(first);
ReadWriteLock lock2 = lockStore.get(second);
if (lock1 == null || lock2 == null) throw new IllegalArgumentException("Account not found");
try {
if (!lock1.writeLock().tryLock(300, TimeUnit.MILLISECONDS)) return false;
try {
if (!lock2.writeLock().tryLock(300, TimeUnit.MILLISECONDS)) return false;
try {
Account a1 = accountStore.get(from);
Account a2 = accountStore.get(to);
if (a1.balance.compareTo(amount) < 0) return false;
Thread.sleep(20); // simulate business processing
a1.balance = a1.balance.subtract(amount);
a2.balance = a2.balance.add(amount);
return true;
} finally {
lock2.writeLock().unlock();
}
} finally {
lock1.writeLock().unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Transfer interrupted");
}
}
}4. StampedLock
Basic Usage
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private final StampedLock lock = new StampedLock();
private double x, y;
// write lock
public void move(double deltaX, double deltaY) {
long stamp = lock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
}
}
// pessimistic read lock
public double distanceFromOrigin() {
long stamp = lock.readLock();
try {
return Math.sqrt(x * x + y * y);
} finally {
lock.unlockRead(stamp);
}
}
// optimistic read
public double optimisticDistanceFromOrigin() {
long stamp = lock.tryOptimisticRead();
double curX = x, curY = y;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
curX = x;
curY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(curX * curX + curY * curY);
}
}Features:
Three access modes – write, read, and optimistic read.
Optimistic reads do not block writers, ideal for read‑heavy, write‑light workloads.
All operations return a stamp used for unlocking or validation.
Not re‑entrant and does not support condition variables, but can be converted between modes.
Fairness can be enabled via the constructor (new StampedLock(true)).
5. Condition Variables (Condition)
Basic Usage
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Object[] items = new Object[100];
private int putPtr, takePtr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // wait until not full
}
items[putPtr] = x;
if (++putPtr == items.length) putPtr = 0;
++count;
notEmpty.signal(); // signal that queue is not empty
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // wait until not empty
}
Object x = items[takePtr];
if (++takePtr == items.length) takePtr = 0;
--count;
notFull.signal(); // signal that queue is not full
return x;
} finally {
lock.unlock();
}
}
}Business Scenario – E‑commerce Order Processing
A producer‑consumer model handles order creation and fulfillment. Producers place orders into a bounded queue; consumers retrieve orders for payment and inventory deduction. Condition variables allow precise signaling for "queue not full" and "queue not empty" states, supporting batch operations and reducing lock contention.
public class OrderQueue {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // queue not full
private final Condition notEmpty = lock.newCondition(); // queue not empty
private final Queue<Order> queue = new LinkedList<>();
private final int capacity;
public OrderQueue(int capacity) { this.capacity = capacity; }
public void putBatch(List<Order> orders) throws InterruptedException {
lock.lock();
try {
while (queue.size() + orders.size() > capacity) {
notFull.await();
}
queue.addAll(orders);
System.out.printf("%s produced %d orders, size %d/%d%n",
Thread.currentThread().getName(), orders.size(), queue.size(), capacity);
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public List<Order> takeBatch(int batchSize) throws InterruptedException {
lock.lock();
try {
while (queue.size() < batchSize) {
notEmpty.await();
}
List<Order> batch = new ArrayList<>(batchSize);
for (int i = 0; i < batchSize; i++) {
batch.add(queue.poll());
}
System.out.printf("%s consumed %d orders, size %d/%d%n",
Thread.currentThread().getName(), batchSize, queue.size(), capacity);
notFull.signalAll();
return batch;
} finally {
lock.unlock();
}
}
// Order class omitted for brevity
}Key advantages of using multiple Condition objects include precise wake‑up semantics (signalAll for producers or consumers only) and support for interruptible and timed waits, which are not possible with Object.wait()/notify().
Conclusion
The article systematically compares Java's lock primitives, highlighting when to prefer the simplicity of synchronized, the flexibility of ReentrantLock, the read‑write separation of ReadWriteLock, the high‑performance optimistic reads of StampedLock, and the richer coordination offered by Condition variables. Real‑world code samples illustrate design decisions such as fine‑grained locking per business entity, lock fairness trade‑offs, timeout handling, deadlock avoidance via ordered lock acquisition, and batch processing to reduce contention.
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.
The Dominant Programmer
Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi
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.
