rodolk
rodolk

Reputation: 5907

How does Java non-fair ReentrantReadWriteLock really work?

I have written the following test code in Java using ReentrantReadWriteLock to understand the difference between fair and non-fair mode. However, I see in both modes the result and output is always the same. It seems it's always working in fair mode. Can anybody explain in which case fair and non-fair mode will result in different behaviors?

package lockTest;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MyLockTest {
    static private ReadWriteLock myLock = new ReentrantReadWriteLock(false);

    public class Reader extends Thread {
        int val_;
        public Reader(int val) {
            val_ = val;
        }
        public void run() {
            if (val_ > 0) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

            myLock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + ": Reader inside critical section - val: " + val_ + "-----");
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            myLock.readLock().unlock();
        }
    }

    public class Writer extends Thread {
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            myLock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + ": Writer inside critical section *****");
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            myLock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
        MyLockTest test1 = new MyLockTest();
        MyLockTest.Reader reader1 = test1.new Reader(0);
        MyLockTest.Writer writer1 = test1.new Writer();
        MyLockTest.Reader reader2 = test1.new Reader(1);

        reader2.start();
        writer1.start();
        reader1.start();
    }
}

The output is always:

Thread-0: Reader inside critical section - val: 0-----
Thread-1: Writer inside critical section *****
Thread-2: Reader inside critical section - val: 1-----

The output above is what I expect to see when I change Lock creation to fair mode:

static private ReadWriteLock myLock = new ReentrantReadWriteLock(true);

For non-fair mode I would expect to see the following output:

Thread-0: Reader inside critical section - val: 0-----
Thread-2: Reader inside critical section - val: 1-----
Thread-1: Writer inside critical section *****

Upvotes: 1

Views: 363

Answers (1)

Daniele
Daniele

Reputation: 2837

Using "fair" versus "non-fair" mode affects how the lock is assigned to threads in case of contention.

From the Javadoc for ReentrantReadWriteLock: using the "non-fair" mode, the order of entry to the read and write lock is unspecified, while using the "fair" mode, threads contend for entry using an approximately arrival-order policy.


We can see how using fair/non-fair affects program execution by having some thread contend over the same lock; see the program below.

Running the sample code, a ReentrantWriteLock is contended by different threads; after 1000 lock operations, we dump how many times each thread acquired the lock.

In case USE_FAIR=false is used, counts are random, and a possible output is:

Thread thread-B finished, count=920
Thread thread-A finished, count=79
Thread thread-D finished, count=0
Thread thread-C finished, count=0

in case USE_FAIR=true is used, the output is always like

Thread thread-D finished, count=249
Thread thread-A finished, count=250
Thread thread-C finished, count=250
Thread thread-B finished, count=250

Sample code

package sample1;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class UseLock {

  public static void main(String[] args) {
    UseLock o = new UseLock();
    o.go();
  }

  private void go() {
    TotalPermits tp = new TotalPermits();

    tp.lock.writeLock().lock();

    Contender a = new Contender(tp, "thread-A");
    Contender b = new Contender(tp, "thread-B");
    Contender c = new Contender(tp, "thread-C");
    Contender d = new Contender(tp, "thread-D");

    a.start();
    b.start();
    c.start();
    d.start();

    tp.lock.writeLock().unlock();
  }
}

class TotalPermits {
  private static final boolean USE_FAIR = true;

  private int count = 1_000;
  ReentrantReadWriteLock lock = new ReentrantReadWriteLock(USE_FAIR);

  public boolean get() {
    try {
      lock.writeLock().lock();
      try {
        Thread.sleep(1);
      } catch (InterruptedException e) { }
      return --count>0;
    } finally {
      lock.writeLock().unlock();
    }
  }
}

class Contender extends Thread {
  private int count = 0;
  final String name;
  final TotalPermits tp;

  Contender(TotalPermits tp, String name) {
    this.tp = tp;
    this.name = name;
  }

  @Override
  public void run() {
    while ( tp.get() ) {
      count++;
    }
    System.out.printf("Thread %s finished, count=%d%n", name, count);
  }
}

Note:

The sample code above uses a "write" lock, which can only be held by one thread at a time. So we can use that to divide N permits across the contenders. On the other hand, the "read" lock can be held by multiple threads, as long as none is holding the write lock.

Upvotes: 3

Related Questions