10

ReentrantReadWriteLock has a fair and non-fair(default) mode, but the document is so hard for me to understand it.

How can I understand it? It's great if there is some code example to demo it.

UPDATE

If I have a writing thread, and many many reading thread, which mode is better to use? If I use non-fair mode, is it possible the writing thread has little chance to get the lock?

Freewind
  • 193,756
  • 157
  • 432
  • 708
  • 1
    It would be helpful if you could explain what about it you don't understand. – Matt Ball Nov 01 '11 at 04:00
  • 1
    the source code is available, yet to put it simple: threads that already have been waiting do not enjoy any privileges compared to threads that are just attempting to obtain the lock. – bestsss Nov 01 '11 at 04:05
  • I want to know if thread_1 do read operation and while this read operation 2 other threads want to do read and write. Will read operation had priority over write operation? – gstackoverflow Mar 13 '17 at 08:04

2 Answers2

17

Non-fair means that when the lock is ready to be obtained by a new thread, the lock gives no guarantees to the fairness of who obtains the lock (assuming there are multiple threads requesting the lock at the time). In other words, it is conceivable that one thread might be continuously starved because other threads always manage to arbitrarily get the lock instead of it.

Fair mode acts more like first-come-first-served, where threads are guaranteed some level of fairness that they will obtain the lock in a fair manner (e.g. before a thread that started waiting long after).

Edit

Here is an example program that demonstrates the fairness of locks (in that write lock requests for a fair lock are first come, first served). Compare the results when FAIR = true (the threads are always served in order) versus FAIR = false (the threads are sometimes served out of order).

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class FairLocking {

    public static final boolean FAIR = true;
    private static final int NUM_THREADS = 3;

    private static volatile int expectedIndex = 0;

    public static void main(String[] args) throws InterruptedException {
        ReentrantReadWriteLock.WriteLock lock = new ReentrantReadWriteLock(FAIR).writeLock();

        // we grab the lock to start to make sure the threads don't start until we're ready
        lock.lock();

        for (int i = 0; i < NUM_THREADS; i++) {
            new Thread(new ExampleRunnable(i, lock)).start();

            // a cheap way to make sure that runnable 0 requests the first lock
            // before runnable 1
            Thread.sleep(10);
        }

        // let the threads go
        lock.unlock();
    }

    private static class ExampleRunnable implements Runnable {
        private final int index;
        private final ReentrantReadWriteLock.WriteLock writeLock;

        public ExampleRunnable(int index, ReentrantReadWriteLock.WriteLock writeLock) {
            this.index = index;
            this.writeLock = writeLock;
        }

        public void run() {
            while(true) {
                writeLock.lock();
                try {
                    // this sleep is a cheap way to make sure the previous thread loops
                    // around before another thread grabs the lock, does its work,
                    // loops around and requests the lock again ahead of it.
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    //ignored
                }
                if (index != expectedIndex) {
                    System.out.printf("Unexpected thread obtained lock! " +
                            "Expected: %d Actual: %d%n", expectedIndex, index);
                    System.exit(0);
                }

                expectedIndex = (expectedIndex+1) % NUM_THREADS;
                writeLock.unlock();
            }
        }
    }
}

Edit (again)

Regarding your update, with non-fair locking it's not that there's a possibility that a thread will have a low chance of getting a lock, but rather that there's a low chance that a thread will have to wait a bit.

Now, typically as the starvation period increases, the probability of that length of time actually occuring decreases...just as flipping a coin "heads" 10 consecutive times is less likely to occur than flipping a coin "heads" 9 consecutive times.

But if the selection algorithm for multiple waiting threads was something non-randomized, like "the thread with the alphabetically-first name always gets the lock" then you might have a real problem because the probability does not necessarily decrease as the thread gets more and more starved...if a coin is weighted to "heads" 10 consecutive heads is essentially as likely as 9 consecutive heads.

I believe that in implementations of non-fair locking a somewhat "fair" coin is used. So the question really becomes fairness (and thus, latency) vs throughput. Using non-fair locking typically results in better throughput but at the expense of the occasional spike in latency for a lock request. Which is better for you depends on your own requirements.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • 3
    *Using non-fair locking typically results in slightly better throughput* actually in much better – bestsss Nov 01 '11 at 07:57
  • @best: Thanks, I haven't tested that for myself so I don't know what the real results typically are. – Mark Peters Nov 01 '11 at 13:27
  • 2
    some link (a bit old) by Brian Goetz: http://www.ibm.com/developerworks/java/library/j-jtp10264/ basically the contended locks destroyed performance quite noticeably, however using the fair version prevents a running thread to obtain the lock but instead it has to get parked (goes to kernel) and unpark another, which in effect has to content for the lock vs some other thread and so on. – bestsss Nov 01 '11 at 14:27
  • It is really nice example. Thanks a lot! – fashuser Oct 21 '14 at 07:32
  • 1
    But why did you use volatile indicator for expectedIndex variable? I guess it doesn't require for the following case because lock instance will perform needed synchronization. Namely all shared variables that have been accessed from main memory will update automatically. – fashuser Oct 21 '14 at 19:45
  • @Mark Peters why didn't you do unlock in finally? – gstackoverflow Mar 13 '17 at 08:44
  • @gstackoverflow: Just for simplicity of the example. This example is just to demonstrate locking fairness; it shouldn't be taken as an example of how to use locking. – Mark Peters Mar 13 '17 at 17:56
0

When some threads waiting for a lock, and the lock has to select one thread to get the access to the critical section:
In non-fair mode, it selects thread without any criteria.
In fair mode, it selects thread that has waiting for the most time.

Note: Take into account that the behavior explained previously is only used with the lock() and unlock() methods. As the tryLock() method doesn't put the thread to sleep if the Lock interface is used, the fair attribute doesn't affect its functionality.

zxholy
  • 401
  • 3
  • 9