blob: e77f86cf2a0cacb8902406d152de75b7e0168e73 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2010-2011, International Business Machines
* Corporation and others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.impl;
import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentHashMap;
/**
* Generic, thread-safe cache implementation, storing SoftReferences to cached instances.
* To use, instantiate a subclass which implements the createInstance() method,
* and call get() with the key and the data. The get() call will use the data
* only if it needs to call createInstance(), otherwise the data is ignored.
*
* By using SoftReferences to instances, the Java runtime can release instances
* once they are not used any more at all. If such an instance is then requested again,
* the get() method will call createInstance() again and also create a new SoftReference.
* The cache holds on to its map of keys to SoftReferenced instances forever.
*
* @param <K> Cache lookup key type
* @param <V> Cache instance value type
* @param <D> Data type for creating a new instance value
*
* @author Markus Scherer, Mark Davis
*/
public abstract class SoftCache<K, V, D> extends CacheBase<K, V, D> {
@Override
public final V getInstance(K key, D data) {
// We synchronize twice, once on the map and once on valueRef,
// because we prefer the fine-granularity locking of the ConcurrentHashMap
// over coarser locking on the whole cache instance.
// We use a SettableSoftReference (a second level of indirection) because
// ConcurrentHashMap.putIfAbsent() never replaces the key's value, and if it were
// a simple SoftReference we would not be able to reset its value after it has been cleared.
// (And ConcurrentHashMap.put() always replaces the value, which we don't want either.)
SettableSoftReference<V> valueRef = map.get(key);
V value;
if(valueRef != null) {
synchronized(valueRef) {
value = valueRef.ref.get();
if(value != null) {
return value;
} else {
// The instance has been evicted, its SoftReference cleared.
// Create and set a new instance.
value = createInstance(key, data);
if (value != null) {
valueRef.ref = new SoftReference<V>(value);
}
return value;
}
}
} else /* valueRef == null */ {
// We had never cached an instance for this key.
value = createInstance(key, data);
if (value == null) {
return null;
}
valueRef = map.putIfAbsent(key, new SettableSoftReference<V>(value));
if(valueRef == null) {
// Normal "put": Our new value is now cached.
return value;
} else {
// Race condition: Another thread beat us to putting a SettableSoftReference
// into the map. Return its value, but just in case the garbage collector
// was aggressive, we also offer our new instance for caching.
return valueRef.setIfAbsent(value);
}
}
}
/**
* Value type for cache items: Has a SoftReference which can be set
* to a new value when the SoftReference has been cleared.
* The SoftCache class sometimes accesses the ref field directly.
*
* @param <V> Cache instance value type
*/
private static final class SettableSoftReference<V> {
private SettableSoftReference(V value) {
ref = new SoftReference<V>(value);
}
/**
* If the SoftReference has been cleared, then this replaces it with a new SoftReference
* for the new value and returns the new value; otherwise returns the current
* SoftReference's value.
* @param value Replacement value, for when the current reference has been cleared
* @return The value that is held by the SoftReference, old or new
*/
private synchronized V setIfAbsent(V value) {
V oldValue = ref.get();
if(oldValue == null) {
ref = new SoftReference<V>(value);
return value;
} else {
return oldValue;
}
}
private SoftReference<V> ref; // never null
}
private ConcurrentHashMap<K, SettableSoftReference<V>> map =
new ConcurrentHashMap<K, SettableSoftReference<V>>();
}