Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ehcache

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
}
like image 1000
Leszek Pachura Avatar asked Jun 10 '26 04:06

Leszek Pachura


1 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
}
like image 53
Henri Avatar answered Jun 11 '26 21:06

Henri



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!