2

Recently I was involved in the discussion where I was given a cache which needs to be implemented to support below operation:

int getData(String key){
     if(cache.get(key) !=null){
             return cache.get(key);
     } else {
         int val = getValueFromDB();  // line no. 5
         cache.put(key, val);        // line no. 6
         return val;
     }

Now the question is, in a multi-threaded scenario, to implement cache, which would you use: HashMap or ConcurrentHashMap?

My ans: ConcurrentHashMap, as it allows read operation on same and different segment and allows write operations only on different segment.

The argument here by lead was, since key is same, multiple threads will execute to line 5 but only one will perform `line no. 6. Since it is a DB call, it needs to executed just once to put the value on cache if not present.

Me: I can make getData sync.

Lead: Then why ConcurrentHashMap? Normal HashMap would also do.

Me: I'll put line no. 5 and line no. 6 inside sync block.

Lead: Then multiple threads would wait at the block. When one thread executes and notifies others, next thread would execute the db call.

Now, How can we achieve this? Basically, we do not want to execute multiple db calls. It should be done by one thread with just one call.

Please advise.

roger_that
  • 9,493
  • 18
  • 66
  • 102
  • I don't understand why "key is same". Surely different threads will get for different keys? Also not true in that code that 'only one will perform line no. 6'. Two threads accessing same key could certainly find no entry and start accessing the database. Indeed if db access takes a while could end up with a queue of threads fetching the same key. Putting it in a `synchronized block` will entirely defeat the purpose of using ConcurrentHashMap making write operations sequential. The answers below `computeIfAbsent()` fix that. – Persixty Aug 31 '17 at 09:03
  • And of course if using `HashMap` would need to `synchronized` reads as well (or implement a `ReadWriteLock`). – Persixty Aug 31 '17 at 09:13
  • Got it. Was not aware of `computeIfAbsent()` method. Is it part of Java 8? – roger_that Aug 31 '17 at 09:21
  • 2
    Yes Java 1.8 and couldn't exist before because uses a lambda expression. Do be aware of locking consequences of injecting code inside a locked method. Specifically all access to that stripe of the cache will be blocked **while the database is accessed**. A high scalability solution would add a placeholder and then individual threads would pend on a condition of the result being added. May be over engineering. – Persixty Aug 31 '17 at 13:41

2 Answers2

7

The answer here is to use ConcurrentHashMap's computeIfAbsent(). This method is implemented to get the value for the key from the Map, and if it is absent, to calculate it, given the provided mapping Function. On ConcurrentHashMap it will do this atomically.

So in your case, the function would be implemented to fetch the entry from the DB. As this happens atomically, you're guaranteed for it to happen only once.

Like this :

int getData(String key){
    return cache.computeIfAbsent(key, k -> getValueFromDb());
}
bowmore
  • 10,842
  • 1
  • 35
  • 43
6

He's right on pretty much everything. The problem you're having is that you're not taking full advantage of the features ConcurrentHashMap provides.

You're using regular methods of Map - get() and put() - which results in a "check-then-act".

Needless to say, this problem has already been solved: there's a computeIfAbsent method which does pretty much exactly what you need:

int getData(String key){
     return cache.computeIfAbsent(key, k -> getValueFromDB());
}

I had initially suggested to use putIfAbsent but the problem with this is that the getValueFromDB function will be evaluated regardless of whether its required.

Michael
  • 41,989
  • 11
  • 82
  • 128