3

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 ?

Community
  • 1
  • 1
gstackoverflow
  • 36,709
  • 117
  • 359
  • 710

3 Answers3

2

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.

njr
  • 3,399
  • 9
  • 7
  • from java doc: **When constructed as fair, threads contend for entry using an approximately arrival-order policy. When the currently held lock is released, either the longest-waiting single writer thread will be assigned the write lock, or if there is a group of reader threads waiting longer than all waiting writer threads, that group will be assigned the read lock** I don't see any priority of writer thread here – gstackoverflow Oct 15 '19 at 15:09
  • Thanks for explaining. If focusing on, "Prefers writers over readers", then I agree that's inconsistent with the JavaDoc, which says, "This class does not impose a reader or writer preference ordering for lock access". Their description of the actual behavior following that seemed fine though. Regarding the prevention of writer starvation, giving writer threads priority would be one way to do it, but how is ReentrantReadWriteLock's arrival-based fair mode not also a way of doing so? Non-reentrant reads requested after a write request will need to wait for the write to gain & release the lock. – njr Oct 15 '19 at 18:29
  • please take a look https://stackoverflow.com/a/58434233/2674303 – gstackoverflow Oct 17 '19 at 14:03
  • One technicality to point out here is that the sample code doesn't actually guarantee the order in which the locks are attempted. Although practically speaking, with threads started in that order, it would usually occur that way. The behavior that you observed is consistent with what the JavaDoc describes for fair mode: "either the longest-waiting single writer thread will be assigned the write lock, or if there is a group of reader threads waiting longer than all waiting writer threads, that group will be assigned the read lock." (Char limit reached, continuing in next comment...) – njr Oct 22 '19 at 16:07
  • The surprising thing here is that you didn't enable fair mode and still observed what looks like that behavior. One thing to point out here is that there is nothing in the JavaDoc that would prevent a JDK's ReentrantReadWriteLock implementation from implementing non-fair mode by reusing its fair mode implementation. Non-fair mode is simply unspecified/undocumented order. If a JDK is doing that, I would caution against relying upon a behavior like that. Always enable fair mode explicitly if that is the behavior that you require. – njr Oct 22 '19 at 16:07
  • using fair mode is almost always bad idea because of performance degradation. Maybe fair mode just to order writers lock acquisition. – gstackoverflow Oct 25 '19 at 08:23
2

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

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
1

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.
         */
JoeG
  • 7,191
  • 10
  • 60
  • 105