Is there any WriteLock acquire priority over ReadLock in ReentrantReadWriteLock

1.5k Views Asked by At

I've read java doc: ReentrantReadWriteLock

And I don't see that writeLock has any priority over readLock

But also I've read topics like this: Are read and write locks in ReentrantReadWriteLock somehow related?
and I see there in both answers following phrases:

if lock is held by readers and thread request write lock no more readers are allowed to acquire read lock until thread which has acquired write lock release it.


Prefers writers over readers. That is, if a writer is waiting on the lock, no new readers from other thread are allowed to access the resource. Existing readers can continue to use the resource until they release the lock. This prevents so-called "writer starvation".

And these phrases sounds meaningfully and looks like I've already read it somewhere else.

But obviously it is a contradiction with java doc.

Was it changed in latest JDK ? is it still valid ?

How to prevent writer starvation ?

3

There are 3 best solutions below

6
njr On

ReentrantReadWriteLock describes two modes of operation: fair mode and non-fair mode. The selections that you cited appear to be aiming to describe fair mode. I wouldn't say that either are obviously contradictory. However, I can see where imprecise wording in the first one ("no more readers are allowed") could lead to confusion. I suspect that "more readers" was intended to refer to new readers from other threads, not additional reentrant reads from the same thread, which some might interpret to be the same reader. If interpreted in this way, then they seem consistent with the JavaDoc.

0
gstackoverflow On

I've created sample to check:

public class RWLockTest {
    public static final Logger LOGGER = LoggerFactory.getLogger(RWLockTest.class);

       public static void main(String[] args) {
        SomeClass someClass = new SomeClass();

        Reader readerRunnable = new Reader(someClass);
        Writer writerRunnable = new Writer(someClass);
        //group 1 readers
        for (int i = 0; i < 10; i++) {
            new Thread(readerRunnable).start();
        }

        // 2 writers
        new Thread(writerRunnable).start();
        LOGGER.info("!!!!!!!!!!!!!!!WRITER_1 WAS STARTED!!!!!!!!!!!!!!!");
        new Thread(writerRunnable).start();
        LOGGER.info("!!!!!!!!!!!!!!!WRITER_2 WAS STARTED!!!!!!!!!!!!!!!");

       //group 2 readers            
       for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(readerRunnable);
            LOGGER.info(String.format("%s was submitted", thread.getId()));
            thread.start();
        }
    }

    public static class SomeClass {
        public ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        public void read() {
            readWriteLock.readLock().lock();
            try {
                LOGGER.info(String.format("Read by %s started", Thread.currentThread().getId()));
                Thread.sleep(5000);
                LOGGER.info(String.format("Read by %s finished", Thread.currentThread().getId()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readWriteLock.readLock().unlock();

            }
        }

        public void write() {
            readWriteLock.writeLock().lock();
            try {
                LOGGER.info(String.format("!!!!!!!!!!Write by %s started!!!!!!!!!!!!", Thread.currentThread().getId()));
                Thread.sleep(3000);
                LOGGER.info(String.format("!!!!!!!!!!Write by %s finished!!!!!!!!", Thread.currentThread().getId()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readWriteLock.writeLock().unlock();

            }
        }

    }

    public static class Reader implements Runnable {
        SomeClass someClass;

        public Reader(SomeClass someClass) {
            this.someClass = someClass;
        }

        @Override
        public void run() {
            someClass.read();
        }
    }

    public static class Writer implements Runnable {
        SomeClass someClass;

        public Writer(SomeClass someClass) {
            this.someClass = someClass;
        }

        @Override
        public void run() {
            someClass.write();
        }
    }
}

I tried to run a lot of time and output is typically like this:

16:31:49.037 [main] INFO my.pack.RWLockTest - !!!!!!!!!!!!!!!WRITER_1 WAS STARTED!!!!!!!!!!!!!!!
16:31:49.040 [main] INFO my.pack.RWLockTest - !!!!!!!!!!!!!!!WRITER_2 WAS STARTED!!!!!!!!!!!!!!!
16:31:49.046 [Thread-1] INFO my.pack.RWLockTest - Read by 13 started
16:31:49.046 [main] INFO my.pack.RWLockTest - 24 was submitted
16:31:49.046 [Thread-7] INFO my.pack.RWLockTest - Read by 19 started
16:31:49.046 [Thread-4] INFO my.pack.RWLockTest - Read by 16 started
16:31:49.046 [Thread-5] INFO my.pack.RWLockTest - Read by 17 started
16:31:49.046 [Thread-0] INFO my.pack.RWLockTest - Read by 12 started
16:31:49.046 [Thread-3] INFO my.pack.RWLockTest - Read by 15 started
16:31:49.047 [main] INFO my.pack.RWLockTest - 25 was submitted
16:31:49.046 [Thread-9] INFO my.pack.RWLockTest - Read by 21 started
16:31:49.047 [Thread-8] INFO my.pack.RWLockTest - Read by 20 started
16:31:49.047 [main] INFO my.pack.RWLockTest - 26 was submitted
16:31:49.047 [Thread-2] INFO my.pack.RWLockTest - Read by 14 started
16:31:49.047 [Thread-6] INFO my.pack.RWLockTest - Read by 18 started
16:31:49.047 [main] INFO my.pack.RWLockTest - 27 was submitted
16:31:49.048 [main] INFO my.pack.RWLockTest - 28 was submitted
16:31:49.048 [main] INFO my.pack.RWLockTest - 29 was submitted
16:31:49.048 [main] INFO my.pack.RWLockTest - 30 was submitted
16:31:49.048 [main] INFO my.pack.RWLockTest - 31 was submitted
16:31:49.049 [main] INFO my.pack.RWLockTest - 32 was submitted
16:31:49.049 [main] INFO my.pack.RWLockTest - 33 was submitted
16:31:54.047 [Thread-7] INFO my.pack.RWLockTest - Read by 19 finished
16:31:54.048 [Thread-6] INFO my.pack.RWLockTest - Read by 18 finished
16:31:54.047 [Thread-5] INFO my.pack.RWLockTest - Read by 17 finished
16:31:54.049 [Thread-2] INFO my.pack.RWLockTest - Read by 14 finished
16:31:54.051 [Thread-8] INFO my.pack.RWLockTest - Read by 20 finished
16:31:54.047 [Thread-1] INFO my.pack.RWLockTest - Read by 13 finished
16:31:54.050 [Thread-9] INFO my.pack.RWLockTest - Read by 21 finished
16:31:54.049 [Thread-4] INFO my.pack.RWLockTest - Read by 16 finished
16:31:54.049 [Thread-3] INFO my.pack.RWLockTest - Read by 15 finished
16:31:54.049 [Thread-0] INFO my.pack.RWLockTest - Read by 12 finished
16:31:54.057 [Thread-10] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 22 started!!!!!!!!!!!!
16:31:57.057 [Thread-10] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 22 finished!!!!!!!!
16:31:57.058 [Thread-11] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 23 started!!!!!!!!!!!!
16:32:00.060 [Thread-11] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 23 finished!!!!!!!!
16:32:00.061 [Thread-13] INFO my.pack.RWLockTest - Read by 25 started
16:32:00.061 [Thread-14] INFO my.pack.RWLockTest - Read by 26 started
16:32:00.061 [Thread-12] INFO my.pack.RWLockTest - Read by 24 started
16:32:00.061 [Thread-15] INFO my.pack.RWLockTest - Read by 27 started
16:32:00.061 [Thread-17] INFO my.pack.RWLockTest - Read by 29 started
16:32:00.062 [Thread-19] INFO my.pack.RWLockTest - Read by 31 started
16:32:00.062 [Thread-18] INFO my.pack.RWLockTest - Read by 30 started
16:32:00.061 [Thread-16] INFO my.pack.RWLockTest - Read by 28 started
16:32:00.062 [Thread-20] INFO my.pack.RWLockTest - Read by 32 started
16:32:00.062 [Thread-21] INFO my.pack.RWLockTest - Read by 33 started
16:32:05.060 [Thread-12] INFO my.pack.RWLockTest - Read by 24 finished
16:32:05.060 [Thread-15] INFO my.pack.RWLockTest - Read by 27 finished
16:32:05.060 [Thread-13] INFO my.pack.RWLockTest - Read by 25 finished
16:32:05.060 [Thread-17] INFO my.pack.RWLockTest - Read by 29 finished
16:32:05.060 [Thread-14] INFO my.pack.RWLockTest - Read by 26 finished
16:32:05.062 [Thread-21] INFO my.pack.RWLockTest - Read by 33 finished
16:32:05.062 [Thread-16] INFO my.pack.RWLockTest - Read by 28 finished
16:32:05.062 [Thread-19] INFO my.pack.RWLockTest - Read by 31 finished
16:32:05.062 [Thread-18] INFO my.pack.RWLockTest - Read by 30 finished
16:32:05.062 [Thread-20] INFO my.pack.RWLockTest - Read by 32 finished

What does it mean?

As you can see I do following:

  1. run 10 threads for reading. all 10 threads can work in parallel. Each read takes 5 sec
  2. Then I start 2 writers (1 write takes 3 sec)
  3. Then I start 10 readers

As you can see the delay between the first write was submitted(16:31:49.037 [main] INFO my.pack.RWLockTest - !!!!!!!!!!!!!!!WRITER_1 WAS STARTED!!!!!!!!!!!!!!!) and when the it actually stated((acquired lock)16:31:54.057 [Thread-10] INFO my.pack.RWLockTest - !!!!!!!!!!Write by 22 started!!!!!!!!!!!!) is 5 sec. It is a time of read.

Also you can see that after 2 writers thread were submitted we submitted 10 readers threads with ids from 24 to 33 and all of them were actually started(acquired locks) after writer thread finished their works.

So readers threads with ids from 24 to 33 were submitted at 16:31:49 and at this time readlock was acquired by first 10 readers and looks like they were able to acquire readlock too but looks like there are something inside ReentrantReadWriteLock that prevents it to avoid writer starvation. Eventually the second group of readers were able to acquire lock only at 16:32:00(6 sec after submitting)

I don't know if it is guaranteed but from my tests it always works in a such way. So we have some priority of Writers over Readers although java doc says:

This class does not impose a reader or writer preference ordering for lock access

0
JoeG On

The definitive answer is always in the code, so let's look there.

Here is the constructor (note: the default constructor calls this one with a fair setting of false)

public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

Thus, the only difference is whether the sync attribute contains a FairSync instance or a NonfairSync one. How do those implementations differ?

Here is code from the writerShouldBlock method of the FairSync class:

final boolean writerShouldBlock() {
    return hasQueuedPredecessors();
}

which means that "if there is a line" then the writer blocks and gets in that line (queue). However, this is in stark contrast to the implementation of the NonfairSync class which is:

final boolean writerShouldBlock() {
    return false;
}

Which definitively shows how in non fair mode writers obtain priority over readers.

One final comment concerning writer starvation. In non fair mode this is achieved in the implementation of the companion method: readerShouldBlock. The comments from the code in the NonfairSync class states:

    final boolean readerShouldBlock() {
        /* As a heuristic to avoid indefinite writer starvation,
         * block if the thread that momentarily appears to be head
         * of queue, if one exists, is a waiting writer.  This is
         * only a probabilistic effect since a new reader will not
         * block if there is a waiting writer behind other enabled
         * readers that have not yet drained from the queue.
         */