Search code examples
javamultithreadingsynchronizedreentrantreadwritelock

ReentrantReadWriteLock - many readers at a time, one writer at a time?


I'm somewhat new to multithreaded environments and I'm trying to come up with the best solution for the following situation:

I read data from a database once daily in the morning, and stores the data in a HashMap in a Singleton object. I have a setter method that is called only when an intra-day DB change occurs (which will happen 0-2 times a day).

I also have a getter which returns an element in the map, and this method is called hundreds of times a day.

I'm worried about the case where the getter is called while I'm emptying and recreating the HashMap, thus trying to find an element in an empty/malformed list. If I make these methods synchronized, it prevents two readers from accessing the getter at the same time, which could be a performance bottleneck. I don't want to take too much of a performance hit since writes happen so infrequently. If I use a ReentrantReadWriteLock, will this force a queue on anyone calling the getter until the write lock is released? Does it allow multiple readers to access the getter at the same time? Will it enforce only one writer at a time?

Is coding this just a matter of...

private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock read = readWriteLock.readLock();
private final Lock write = readWriteLock.writeLock();

public HashMap getter(String a) {
    read.lock();
    try {
        return myStuff_.get(a);            
    } finally {
        read.unlock();
    }
}

public void setter() 
{
    write.lock();
    try {
        myStuff_ = // my logic
     } finally {
          write.unlock();
    }
}

Solution

  • Another way to achieve this (without using locks) is the copy-on-write pattern. It works well when you do not write often. The idea is to copy and replace the field itself. It may look like the following:

    private volatile Map<String,HashMap> myStuff_ = new HashMap<String,HashMap>();
    
    public HashMap getter(String a) {
        return myStuff_.get(a);
    }
    
    public synchronized void setter() {
        // create a copy from the original
        Map<String,HashMap> copy = new HashMap<String,HashMap>(myStuff_);
        // populate the copy
        // replace copy with the original
        myStuff_ = copy;
    }
    

    With this, the readers are fully concurrent, and the only penalty they pay is a volatile read on myStuff_ (which is very little). The writers are synchronized to ensure mutual exclusion.