Building a cache is standard practice in most programs to reduce the overhead of re-creating expensive objects. Using a concurrent Hash Map to build a thread safe cache is a widely user practice.
Even though each method call of concurrent Hash Map is thread safe, it is cumbersome to re-do the necessary operations. Let me list these down:
- Compound operations of checking and inserting data into a HashMap – This can result in duplicate objects being created. You may have to use extensive checks using putIfAbsent to avoid these sort of situations.
- Cache expiry – Getting a scheduled thread to clear the cache
- Look at whether you want strong or weak references, strong or weak keys
- Limit as to how many concurrent updates are allowd
Let me introduce MapMaker, a very convenient Factory object present in the Google Collections library. It lets me do everything mentioned in the above list in a single object initialization.
ConcurrentMap<Key, HeavyObject> graphs = new MapMaker()
.concurrencyLevel(10)
.softKeys()
.weakValues()
.expiration(15, TimeUnit.MINUTES)
.makeComputingMap( new Function<Key, HeavyObject>() {
public Graph apply(Key key) {
return createHeavyObject(key);
}
});
Now, all you have to do to use the cache is do a call to the get method:
graphs.get(someKey);
This will create the object, making sure other threads are blocked until the Heavy Object is created properly and the same object is returned for the specific key to any other thread as well.
Note: Make sure you implement the equals() and hashCode() methods if your Key is a complex object.
Hope you find it useful in your Java code.
Was this really just posted today? Google Collections has been deprecated for years, replaced with Guava, and in Guava, MapMaker is also deprecated, replaced with CacheBuilder. A lot has changed in those years!
I’m using an older version of Google Collections and didn’t realize this. Thanks for pointing this out. Will update the article with CacheBuilder when I have time. But, the principle is still the same IMO.