| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * Copyright (C) 2010-2016, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ******************************************************************************* |
| */ |
| package com.ibm.icu.impl; |
| |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| /** |
| * Generic, thread-safe cache implementation, usually storing cached instances |
| * in {@link java.lang.ref.Reference}s via {@link CacheValue}s. |
| * 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. |
| * |
| * <p>When caching instances while the CacheValue "strength" is {@code SOFT}, |
| * the Java runtime can later release these instances once they are not used any more at all. |
| * If such an instance is then requested again, |
| * the getInstance() method will call createInstance() again and reset the CacheValue. |
| * The cache holds on to its map of keys to CacheValues forever. |
| * |
| * <p>A value can be null if createInstance() returns null. |
| * In this case, it must do so consistently for the same key and data. |
| * |
| * @param <K> Cache lookup key type |
| * @param <V> Cache instance value type (must not be a CacheValue) |
| * @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> { |
| private ConcurrentHashMap<K, Object> map = new ConcurrentHashMap<K, Object>(); |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public final V getInstance(K key, D data) { |
| // We synchronize twice, once in the ConcurrentHashMap and |
| // once in valueRef.resetIfCleared(value), |
| // because we prefer the fine-granularity locking of the ConcurrentHashMap |
| // over coarser locking on the whole cache instance. |
| // We use a CacheValue (a second level of indirection) because |
| // ConcurrentHashMap.putIfAbsent() never replaces the key's value, and if it were |
| // a simple Reference 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.) |
| Object mapValue = map.get(key); |
| if(mapValue != null) { |
| if(!(mapValue instanceof CacheValue)) { |
| // The value was stored directly. |
| return (V)mapValue; |
| } |
| CacheValue<V> cv = (CacheValue<V>)mapValue; |
| if(cv.isNull()) { |
| return null; |
| } |
| V value = cv.get(); |
| if(value != null) { |
| return value; |
| } |
| // The instance has been evicted, its Reference cleared. |
| // Create and set a new instance. |
| value = createInstance(key, data); |
| return cv.resetIfCleared(value); |
| } else /* valueRef == null */ { |
| // We had never cached an instance for this key. |
| V value = createInstance(key, data); |
| mapValue = (value != null && CacheValue.futureInstancesWillBeStrong()) ? |
| value : CacheValue.getInstance(value); |
| mapValue = map.putIfAbsent(key, mapValue); |
| if(mapValue == null) { |
| // Normal "put": Our new value is now cached. |
| return value; |
| } |
| // Race condition: Another thread beat us to putting a CacheValue |
| // into the map. Return its value, but just in case the garbage collector |
| // was aggressive, we also offer our new instance for caching. |
| if(!(mapValue instanceof CacheValue)) { |
| // The value was stored directly. |
| return (V)mapValue; |
| } |
| CacheValue<V> cv = (CacheValue<V>)mapValue; |
| return cv.resetIfCleared(value); |
| } |
| } |
| } |