Overview and Implementation of an Upgradable ReentrantReadWriteLock in Java
This article explains the principles of Java's ReentrantReadWriteLock, details lock downgrade and upgrade mechanisms, provides a full source analysis of read and write lock implementations, and presents a custom upgradable lock with test code and performance observations.
ReentrantReadWriteLock is a re‑entrant read‑write lock suitable for scenarios with many reads and few writes. The write lock is exclusive while the read lock is shared, both support re‑entrancy but are mutually exclusive.
The lock state is stored in the low 16 bits for write locks and the high 16 bits for read locks; the lock uses AQS to manage acquisition, queuing, and release. Both read and write locks share the same FIFO queue, allowing lock downgrade (write → read) but not lock upgrade (read → write).
Lock downgrade is demonstrated with a sample code snippet that acquires the write lock, updates a cache, then acquires the read lock before releasing the write lock, ensuring the thread can read the latest data without contention.
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { abstract static class Sync extends AbstractQueuedSynchronizer { // read lock counter class static final class HoldCounter { int count = 0; final long tid = getThreadId(Thread.currentThread()); } static final class ThreadLocalHoldCounter extends ThreadLocal { public HoldCounter initialValue() { return new HoldCounter(); } } private transient ThreadLocalHoldCounter readHolds; private transient HoldCounter cachedHoldCounter; private transient Thread firstReader = null; private transient int firstReaderHoldCount; } ... }
When a thread holds the write lock, the read‑lock count is zero. During downgrade, the thread first acquires the read lock (which inserts it at the head of the queue), then releases the write lock, allowing it to continue reading while other threads may acquire read locks.
The article then analyses the source code for read‑lock acquisition and release, showing how sync.acquireShared(1) and sync.releaseShared(arg) interact with AQS, thread‑local counters, and fairness policies.
Similarly, the write‑lock implementation is examined, highlighting exclusive acquisition via sync.acquire(1) , re‑entrancy checks, and the role of tryAcquire and tryRelease in managing the lock state.
Because the standard ReentrantReadWriteLock does not support lock upgrade, a custom UpgradableReentrantReadWriteLock is introduced. The class tracks per‑thread read‑lock counts, uses atomic flags to coordinate upgrade attempts, and provides methods readLock() , readUnlock() , writeLock() , and writeUnlock() . Upgrade is performed by releasing all but one read lock, waiting for other readers to finish, then trying to acquire the write lock.
// Upgradable ReentrantReadWriteLock public class UpgradableReentrantReadWriteLock { private final AtomicInteger bargeCount = new AtomicInteger(0); public final AtomicInteger bargeTime = new AtomicInteger(0); private final ReentrantReadWriteLock lock; private final ThreadLocal lockCounters = ThreadLocal.withInitial(() -> new int[]{0,0}); private AtomicBoolean tryingLockUpgrade = new AtomicBoolean(false); private final Boolean newThreadShouldWait = true; private final Boolean letOtherThreadPass = true; // ... methods readLock, readUnlock, writeLock, writeUnlock, lockUpgrade, etc. }
A test program creates 50 reader threads and 10 writer threads. Readers increment a shared counter, and when the counter exceeds 10 they attempt a lock upgrade to reset the counter to zero. The test records upgrade attempts, barging occurrences, and prints statistics.
public class MyTest implements Runnable { // setup of UpgradableReentrantReadWriteLock, CountDownLatch, etc. public void run() { // reader logic with lock.readLock(), lock.writeLock() for upgrade } } public class MySubTest implements Runnable { // writer logic } public static void main(String[] args) throws InterruptedException { // start threads, await completion, print upgradeCount and barging probability }
Typical output shows many read threads being "barged" (forced to retry) during upgrade, an upgrade count close to the expected number of attempts, and a barging probability around 4‑5 % for fair locks, rising significantly for non‑fair locks.
The article concludes that the custom upgradable lock works, but lock upgrade incurs contention and a non‑trivial failure probability, especially under non‑fair scheduling.
YunZhu Net Technology Team
Technical practice sharing from the YunZhu Net Technology Team
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.