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.
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.
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.
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.
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.
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)
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".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With