The Pure Danger of Unfair Read-Write Locks

March 13, 2009

Attending the local No Fluff Just Stuff conference in Saint Louis on Friday, I raised a question during Alex Miller’s talk on Java Concurrency Idioms regarding the documented behavior of the ReentrantReadWriteLock of “A nonfair lock that is continously contended may indefinitely postpone one or more reader or writer threads, […]

So I commented on Alex’s “Writing while reads pile up” blog entry, saying:

My question on Friday was regarding the potential for a sequential set of overlapping readers locking out a waiting writer forever. It seems that this could happen, as the JavaDoc explicitly says, “A nonfair lock that is continously contended may indefinitely postpone one or more reader or writer threads”.
The scenario I had in mind would work like this:
1. Reader 1 gets a read lock.
2. The writer requests a write lock, and is blocked.
3. Reader 2 requests a read lock — and let’s say they get it.
4. Reader 1 releases their read lock.
5. Reader 3 requests a read lock — and let’s say they get it.
6. Reader 2 releases their read lock.
As long as steps 5 and 6 are repeated as shown above, the writer would be delayed indefinately.
However, in writing tests for this, I’m finding that the ‘java.util.concurrent.locks.ReentrantReadWriteLock’ class has the (apparently undocumented) behavior that it stops granting read locks when a write lock is requested. So it appears that even in non-fair mode, readers will NOT block writers indefinitely. (…as long as every lock holder releases their lock within a reasonable period of time!)
What I find is that in step 3 above, Reader 2 is delayed until the writer gets, uses and releases the write lock. So the write does NOT get indefinitely postponed in this scenario.

Not wanting to keep secrets, I am posting here the code I’m using to come to this conclusion:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.junit.Test;

public class LockTest {
private List readThreads = new ArrayList();

public void runTest() throws InterruptedException {
final boolean isFair = false;
final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(isFair);
final ReadLock readLock = readWriteLock.readLock();
final WriteLock writeLock = readWriteLock.writeLock();

System.out.println(“–> Start working read threads.”);
final int readersPerBatch = 5;
for (int idx = 0; idx Start blocking read threads.”);
for (int idx = 0; idx >> WRITE LOGIC HERE <<<“);
System.out.println(“Main: Released write lock.”);

for (final Thread thread : readThreads) {

private void startReadLockThread(final ReadLock readLock, final int thisReaderNumber) {
final Thread thread = new Thread(new Runnable() {
public void run() {
final String messagePrefix = “Reader ” + thisReaderNumber + “: “;
System.out.println(messagePrefix + “Started.”);
System.out.println(messagePrefix + “Asking for read lock.”);
try {
System.out.println(messagePrefix + “Has read lock.”);
System.out.println(messagePrefix + “‘Working’ with read lock.”);
} finally {
System.out.println(messagePrefix + “Releasing read lock.”);

private static void sleepSeconds(double seconds) {
try {
final long milliseconds = Math.round(seconds * 1000);
} catch (InterruptedException ex) {

Sample Output:

--> Start working read threads.
Reader 1: Started.
Reader 3: Started.
Reader 2: Started.
Main: Sleeping.
Reader 5: Started.
Reader 4: Started.
Reader 5: Asking for read lock.
Reader 5: Has read lock.
Reader 5: 'Working' with read lock.
Reader 3: Asking for read lock.
Reader 3: Has read lock.
Reader 3: 'Working' with read lock.
Reader 1: Asking for read lock.
Reader 1: Has read lock.
Reader 1: 'Working' with read lock.
Reader 4: Asking for read lock.
Reader 4: Has read lock.
Reader 4: 'Working' with read lock.
Reader 2: Asking for read lock.
Reader 2: Has read lock.
Reader 2: 'Working' with read lock.
--> Start blocking read threads.
Reader 6: Started.
Reader 8: Started.
Reader 7: Started.
Reader 9: Started.
Main: Attempting to acquire write lock.
Reader 10: Started.
Reader 8: Asking for read lock.
Reader 10: Asking for read lock.
Reader 9: Asking for read lock.
Reader 7: Asking for read lock.
Reader 6: Asking for read lock.
Reader 2: Releasing read lock.
Reader 4: Releasing read lock.
Reader 1: Releasing read lock.
Reader 3: Releasing read lock.
Reader 5: Releasing read lock.
Main: Has write lock.
Main: >>> WRITE LOGIC HERE <<<
Main: Released write lock.
Reader 8: Has read lock.
Reader 8: 'Working' with read lock.
Reader 10: Has read lock.
Reader 10: 'Working' with read lock.
Reader 6: Has read lock.
Reader 6: 'Working' with read lock.
Reader 7: Has read lock.
Reader 7: 'Working' with read lock.
Reader 9: Has read lock.
Reader 9: 'Working' with read lock.
Reader 8: Releasing read lock.
Reader 6: Releasing read lock.
Reader 10: Releasing read lock.
Reader 7: Releasing read lock.
Reader 9: Releasing read lock.

Observe that the Main thread starts up five Reader threads, and waits for them to acquire read locks and start “working.” Main then fires up five more read threads and requests a write lock before the new threads request their read locks. Notice that the five new Reader threads WAIT until the first five threads finish, and the main thread acquires, uses and releases the write lock. And then the five Reader threads that have been waiting all get their locks, do their “work” and finish.

This is much better behavior than I had expected.