Search code examples
javareentrantreadwritelock

Why the write threads can't get the lock when ReentrantReadWriteLock is non-fair?


From this question How to understand the “non-fair” mode of ReentrantReadWriteLock?, I think all threads have the same opportunity to get the lock no matter which comes first.

So I write this code to test it:

public static void main(String[] args) {
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
    final ReadLock readLock = lock.readLock();
    final WriteLock writeLock = lock.writeLock();

    // hold the write lock 3s at first
    new Thread() {
        public void run() {
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + " got the write lock");
            quietSleep(3);
            writeLock.unlock();
            System.out.println(Thread.currentThread().getName() + " released the write lock");

        };
    }.start();

    // a thread want to get the read lock 1s later
    new Thread() {
        public void run() {
            quietSleep(1);
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " got the read lock");
        };
    }.start();

    // 1000 threads want to get the write lock 2s later
    for (int i = 0; i < 1000; i++) {
        new Thread() {
            public void run() {
                quietSleep(2);
                writeLock.lock();
                System.out.println(Thread.currentThread().getName() + " got the write lock");
            };
        }.start();
    }
}

private static void quietSleep(int seconds) {
    try {
        Thread.sleep(seconds * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

At first, there is a thread got the write lock, and held it for 3s. During this time, a thread want to get the read lock, and then 1000 threads want to get the write lock.

Since ReentrantReadWriteLock uses non-fair mode by default, I think the write threads have a great opportunity to get the write lock. But I run it many times, and each time read thread won!

The output is:

Thread-0 got the write lock
Thread-0 released the write lock
Thread-1 got the read lock

Do I understanding "non-fair" wrong?


UPDATE Per paxdiablo's answer, I modified my code to:

new Thread() {
    public void run() {
        quietSleep(1);
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + " got the write lock");

    };
}.start();

for (int i = 0; i < 1000; i++) {
    new Thread() {
        public void run() {
            quietSleep(2);
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " got the read lock");
        };
    }.start();
}

Now there is a thread want the write lock and 1000 read threads want the read lock. But the output is:

Thread-0 got the write lock
Thread-0 released the write lock
Thread-1 got the write lock

Seem it's still "first-come-first-get".


Solution

  • Being non-fair just means it doesn't have to hand out the lock in a queued manner (first come, first served). It makes no other guarantees about how it will be handed out. In fact, it may still hand them out in a queued manner if it wants.

    It may be that it prefers handing out out to readers simply because multiple readers can have the lock at the same time but if a writer gets it, all readers and writers are blocked.

    I once had to implement a reader/writer mutex long ago and it had multiple modes depending on what you needed to acheive:

    • prefer reader.
    • prefer writer.
    • prefer alternate reader/writer.
    • queued access.

    It sounds like that first one is what your system may be doing (I say "may" since it could be that your code is running deterministically despite the threaded nature, ie, the same each time).


    If you want to see the difference between fair and non-fair, try the following.

    • Have one thread get a read lock then go to sleep for a minute.
    • During that sleep, have a thread request a write lock the go to sleep for a minute.
    • Then have a loop of ten attempts to get and release a read lock.

    In fair mode, it should be RWRRRRRRRRRR. That's because all but the first read lock will wait until the write lock is obtained and released (the write goes first because that's fair).

    In non-fair mode, you may see RRRRRRRRRRRW, the read locks may all be allowed to jump in front of the write lock because they don't interfere with that first read lock, fairness be damned :-)


    Of course, concepts of fairness may vary depending on the author. It would probably be within the rules for an algorithm to allow reads to sneak in front of writes but with limits. For example, once a write lock is requested, only five more read locks are allowed to sneak through before being queued behind the write lock.

    I'm not saying anyone's ever implemented that but it would certainly be a viable option, balancing efficiency against fairness.