Search This Blog

Sunday 3 August 2014

The ReadWriteLock

The lock interface in Java has a specialized version - ReadWriteLock. As per the interface documentation:
A ReadWriteLock maintains a pair of associated locks, one for read-only operations 
and one for writing. The read lock may be held simultaneously by multiple reader 
threads, so long as there are no writers.The write lock is exclusive.
All ReadWriteLock implementations must guarantee that the memory synchronization 
effects of writeLock operations (as specified in the Lock interface) also hold 
with respect to the associated readLock. That is, a thread successfully acquiring 
the read lock will see all updates made upon previous release of the write lock
To test the same class I created a StockTracker class that keeps track of the Stock values.
class StockTracker {
  private final Map<String, Float> tickers = new HashMap<String, Float>();

  private final ReadWriteLock tickerLock = new ReentrantReadWriteLock();
  private final Lock readLock = tickerLock.readLock();
  private final Lock writeLock = tickerLock.writeLock();

  public Float getValue(String caller, String key) {
    Float val = null;
    System.out.println(caller + " trying to read value for " + key 
        + " at timestamp " + System.currentTimeMillis() + "...");

    readLock.lock();
    val = tickers.get(key);
    System.out.println(caller + "  read value for " + key 
        + " at timestamp " + System.currentTimeMillis());
    readLock.unlock();
    return val;
  }

  public void addValue(String caller, String key, Float value) {
    System.out.println(caller + " trying to write value for " + key 
        + " at timestamp " + System.currentTimeMillis() + "...");

    writeLock.lock();
    tickers.put(key, value);
    System.out.println(caller + "  wrote value for " + key 
        + " at timestamp " + System.currentTimeMillis());
    writeLock.unlock();
  }

}
As seen there are two methods - the getValue() used by readers and the addValue() used by writers. The lock itself is composed of two locks:
    private final ReadWriteLock tickerLock = new ReentrantReadWriteLock();
    private final Lock readLock = tickerLock.readLock();
    private final Lock writeLock = tickerLock.writeLock();
For the read operation we accordingly used the readLock and for write we used the writeLock. my writer class is as below:
class Agent implements Runnable {

  private String stockName;
  private StockTracker stockTracker;

  public Agent(String stockName, StockTracker stockTracker) {
    this.stockName = stockName;
    this.stockTracker = stockTracker;
  }

  public void run() {
    for (int i = 0; i < 2; i++) {
      stockTracker.getValue(Thread.currentThread().getName(), stockName);
    }
  }

}
similarly the reader is as below:
class StockMarketEmployee implements Runnable {

  private String stockName;
  private StockTracker stockTracker;

  public StockMarketEmployee(String stockName, StockTracker stockTracker) {
    this.stockName = stockName;
    this.stockTracker = stockTracker;
  }

  public void run() {
    for (int i = 0; i < 2; i++) {
      stockTracker.addValue(Thread.currentThread().getName(), 
          stockName, (float) (Math.random() * 100));
    }
  }

}
I decided to run the code:
public static void main(String[] args) {
        StockTracker stockTracker = new StockTracker();
         new Thread(new StockMarketEmployee("IBM", stockTracker), "Emp 1").start();
        new Thread(new Agent("IBM", stockTracker), "Buyer 1").start();
        new Thread(new Agent("GOOG", stockTracker), "Seller 2").start();
    }
The output is:
Buyer 1 trying to read value for IBM at timestamp 1383914697045...
Emp 1 trying to write value for IBM at timestamp 1383914697045...
Buyer 1  read value for IBM at timestamp 1383914697046
Seller 2 trying to read value for GOOG at timestamp 1383914697045...
Emp 1  wrote value for IBM at timestamp 1383914697056
Emp 1 trying to write value for IBM at timestamp 1383914697056...
Emp 1  wrote value for IBM at timestamp 1383914697056
Seller 2  read value for GOOG at timestamp 1383914697056
Buyer 1 trying to read value for IBM at timestamp 1383914697067...
Buyer 1  read value for IBM at timestamp 1383914697067
Seller 2 trying to read value for GOOG at timestamp 1383914697078...
Seller 2  read value for GOOG at timestamp 1383914697078
It would be futile to try and predict anything from the above results. From the java docs:
A read-write lock allows for a greater level of concurrency in accessing shared 
data than that permitted by a mutual exclusion lock. It exploits the fact that 
while only a single thread at a time (a writer thread) can modify the shared data,
in many cases any number of threads can concurrently read the data (hence reader threads).
Whether or not a read-write lock will improve performance over the use of a mutual 
exclusion lock depends on the frequency that the data is read compared to being modified, 
the duration of the read and write operations, and the contention for the data - that is,
the number of threads that will try to read or write the data at the same time. For 
example, a collection that is initially populated with data and thereafter infrequently 
modified, while being frequently searched (such as a directory of some kind) is an ideal
candidate for the use of a read-write lock. However, if updates become frequent then 
the data spends most of its time being exclusively locked and there is little, if 
any increase in concurrency.

No comments:

Post a Comment