Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing a cache using a java ConcurrentHashMap

I'd like to implement a simple caching of heavyweight objects in a web java application. But I can't figure out how to do it properly.

Am I missing something or ConcurrentHashMap methods (putIfAbsent, ...) are not enough and additional synchronization is needed ?

Is there a better simple API (In memory storage, no external config) to do this ?

P.

like image 632
Paolo1976 Avatar asked Jan 15 '10 08:01

Paolo1976


People also ask

How is a ConcurrentHashMap implemented?

In ConcurrentHashMap, at a time any number of threads can perform retrieval operation but for updated in the object, the thread must lock the particular segment in which the thread wants to operate. This type of locking mechanism is known as Segment locking or bucket locking.

What is the ConcurrentHashMap in Java and do you implement it?

Java ConcurrentHashMap class is part of the Concurrency Collection Classes. It's a hash table implementation, which supports concurrent retrieval and updates. It's used in a multi-threaded environment to avoid ConcurrentModificationException.

How is ConcurrentHashMap implemented internally?

ConcurrentHashMap: It allows concurrent access to the map. Part of the map called Segment (internal data structure) is only getting locked while adding or updating the map. So ConcurrentHashMap allows concurrent threads to read the value without locking at all. This data structure was introduced to improve performance.

Why ConcurrentHashMap is faster than HashMap?

HashMap performance is relatively high because it is non-synchronized in nature and any number of threads can perform simultaneously. But ConcurrentHashMap performance is low sometimes because sometimes Threads are required to wait on ConcurrentHashMap.


2 Answers

Further to Ken's answer, if creating a heavyweight object which later gets thrown away is NOT acceptable (you want to guarantee that only one object gets created for each key, for some reason), then you can do this by.... actually, don't. Don't do it yourself. Use the google-collections (now guava) MapMaker class:

Map<KeyType, HeavyData> cache = new MapMaker<KeyType, HeavyData>()
  .makeComputingMap(new Function<KeyType, HeavyData>() {
      public HeavyData apply(KeyType key) {
          return new HeavyData(key); // Guaranteed to be called ONCE for each key
      }
  });

Then a simple cache.get(key) just works and completely removes you from having to worry about tricky aspects of concurrency and syncrhonization.

Note that if you want to add some fancier features, like expiry, it's just

Map<....> cache = new MapMaker<....>()
  .expiration(30, TimeUnit.MINUTES)
  .makeComputingMap(.....)

and you can also easily use soft or weak values for either keys or data if required (see the Javadoc for more details)

like image 200
Cowan Avatar answered Sep 29 '22 23:09

Cowan


If it is safe to temporarily have more than one instance for the thing you're trying to cache, you can do a "lock-free" cache like this:

public Heavy instance(Object key) {
  Heavy info = infoMap.get(key);
  if ( info == null ) {
    // It's OK to construct a Heavy that ends up not being used
    info = new Heavy(key);
    Heavy putByOtherThreadJustNow = infoMap.putIfAbsent(key, info);
    if ( putByOtherThreadJustNow != null ) {
      // Some other thread "won"
      info = putByOtherThreadJustNow;
    }
    else {
      // This thread was the winner
    }
  }
  return info;
}

Multiple threads can "race" to create and add an item for the key, but only one should "win".

like image 40
Ken Avatar answered Sep 30 '22 00:09

Ken