Friday, 16 September 2016

Java 5 - ReadWriteLock for better performance - ReentrantReadWriteLock and StampedLock


ReadWriteLock was introduced in Java 5 in the form of ReentrantReadWriteLock. A readWriteLock improves performance significantly in a multi-threaded environment when a resource is read more often than it is written.

The concept is simple, multiple threads can do read simultaneously and does not need to be blocked on each other, but if a write operation is going on all the subsequent write/read locks need to wait. A write lock will also wait for already acquired read locks before it gains the exclusive lock on the resource.

A typical use case of ReadWriteLock comes while maintaining a cache that needs to be updated when a new value gets added to the system.

An example cache managed by ReadWriteLock is as follows:


package com.prasune.test.concurrent;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CachedDataManager {

    private static Map<String, Object> cachedData = new HashMap<>();

    /**
     * Read/write lock to manage cache read/write operations.
     */
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /**
     * Singleton instance
     */
    private static CachedDataManager instance = new CachedDataManager();

    /**
     * private constructor to make class singleton
     */
    private CachedDataManager() {

    }
   
    public CachedDataManager getInstance() {
        return instance;
    }

    /**
     * Fetch cache data using key
     * @param key
     * @return
     */
    public Object getCachedData(String key) {
        Object data = null;
        try {
            readWriteLock.readLock().lock();
            data = cachedData.get(key);           
        } finally {
            readWriteLock.readLock().unlock();
        }
        return data;
    }
   
    /**
     * Update cache  by adding new entry
     * @param key
     * @param data
     */
    public void addToCache(String key, Object data) {
        try {
            readWriteLock.writeLock().lock();
            cachedData.put(key, data);           
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
}




ReentrantReadWriteLock performs better than the traditional lock mechanism, but it is not fast enough and is too slow at times. So, Java 8 introduced a new ReadWriteLock in the form of StampedLock which uses new set of algorithms and memory management introduced in Java 8.

StampedLock uses a stamp to identify the lock which helps in better performance in managing the read/write lock.

Let us modify our program to use StampedLock:


package com.prasune.test.concurrent;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;

public class CachedDataManager {

    private static Map<String, Object> cachedData = new HashMap<>();

    /**
     * Read/write lock to manage cache read/write operations.
     */
    private final StampedLock readWriteLock = new StampedLock();

    /**
     * Singleton instance
     */
    private static CachedDataManager instance = new CachedDataManager();

    /**
     * private constructor to make class singleton
     */
    private CachedDataManager() {

    }
   
    public CachedDataManager getInstance() {
        return instance;
    }

    /**
     * Fetch cache data using key
     * @param key
     * @return
     */
    public Object getCachedData(String key) {
        Object data = null;
        Long stamp = 0L;
        try {
            stamp = readWriteLock.readLock();
            data = cachedData.get(key);           
        } finally {
            readWriteLock.unlockRead(stamp);
        }
        return data;
    }
   
    /**
     * Update cache  by adding new entry
     * @param key
     * @param data
     */
    public void addToCache(String key, Object data) {
        Long stamp = 0L;
        try {
            stamp = readWriteLock.writeLock();
            cachedData.put(key, data);           
        } finally {
            readWriteLock.unlockWrite(stamp);
        }
    }
}