Ehcache & multi-threading: how to lock when inserting to the cache?

Viewed 1040

Let's suppose I have a multi-threading application with 4 threads which share one (Eh)cache; the cache stores UserProfile objects in order to avoid fetching them from the database every time.

Now, let's say all these 4 threads request the same UserProfile with ID=123 at the same moment - and it hasn't been cached yet. What has to be done is to query the database and insert obtained UserProfile object into the cache so it could be reused later.

However, what I want to achieve is that only one of these threads (the first one) queries the database and updates the cache, while the other 3 wait (queue) for it to finish... and then get the UserProfile object with ID=123 directly from cache.

How do you usually implement such scenario? Using Ehcache's locking/transactions? Or rather through something like this? (pseudo-code)

public UserProfile getUserProfile(int id) {
    result = ehcache.get(id)
    if (result == null) {  // not cached yet
        synchronized {  // queue threads
            result = ehcache.get(id)
            if (result == null) {  // is current thread the 1st one?
                result = database.fetchUserProfile(id)
                ehcache.put(id, result)
            }
        }
    }
    return result
}
2 Answers

This is called a Thundering Herd problem.

Locking works but it's really efficient because the lock is broader than what you would like. You could lock on a single ID.

You can do 2 things. One is to use a CacheLoaderWriter. It will load the missing entry and perform the lock at the right granularity. This is the easiest solution even though you have to implement a loader-writer.

The alternative is more involved. You need some kind of row-locking algorithm. For example, you could do something like this:

private final ReentrantLock locks = new ReentrantLocks[1024];
{
    for(int i = 0; i < locks.length; i)) {
        locks[i] = new ReentrantLock();
    }
}

public UserProfile getUserProfile(int id) {
    result = ehcache.get(id)
    if (result == null) {  // not cached yet
        ReentrantLock lock = locks[id % locks.length];
        lock.lock();
        try {
            result = ehcache.get(id)
            if (result == null) {  // is current thread the 1st one?
                result = database.fetchUserProfile(id)
                ehcache.put(id, result)
            }                
        } finally {
            lock.unlock();
        }
    }
    return result
}

Use a plain java object lock :

private static final Object LOCK = new Object();


synchronized (LOCK) {

    result = ehcache.get(id);

    if ( result == null || ehcache.isExpired() ) {  

        // cache is expired or null so going to DB

        result = database.fetchUserProfile(id);

        ehcache.put(id, result)
    }

}
Related