1 /*
2 *******************************************************************************
3 *   Copyright (C) 2010-2011, International Business Machines
4 *   Corporation and others.  All Rights Reserved.
5 *******************************************************************************
6 */
7 package com.ibm.icu.impl;
8 
9 import java.lang.ref.SoftReference;
10 import java.util.concurrent.ConcurrentHashMap;
11 
12 /**
13  * Generic, thread-safe cache implementation, storing SoftReferences to cached instances.
14  * To use, instantiate a subclass which implements the createInstance() method,
15  * and call get() with the key and the data. The get() call will use the data
16  * only if it needs to call createInstance(), otherwise the data is ignored.
17  *
18  * By using SoftReferences to instances, the Java runtime can release instances
19  * once they are not used any more at all. If such an instance is then requested again,
20  * the get() method will call createInstance() again and also create a new SoftReference.
21  * The cache holds on to its map of keys to SoftReferenced instances forever.
22  *
23  * @param <K> Cache lookup key type
24  * @param <V> Cache instance value type
25  * @param <D> Data type for creating a new instance value
26  *
27  * @author Markus Scherer, Mark Davis
28  */
29 public abstract class SoftCache<K, V, D> extends CacheBase<K, V, D> {
30     @Override
getInstance(K key, D data)31     public final V getInstance(K key, D data) {
32         // We synchronize twice, once on the map and once on valueRef,
33         // because we prefer the fine-granularity locking of the ConcurrentHashMap
34         // over coarser locking on the whole cache instance.
35         // We use a SettableSoftReference (a second level of indirection) because
36         // ConcurrentHashMap.putIfAbsent() never replaces the key's value, and if it were
37         // a simple SoftReference we would not be able to reset its value after it has been cleared.
38         // (And ConcurrentHashMap.put() always replaces the value, which we don't want either.)
39         SettableSoftReference<V> valueRef = map.get(key);
40         V value;
41         if(valueRef != null) {
42             synchronized(valueRef) {
43                 value = valueRef.ref.get();
44                 if(value != null) {
45                     return value;
46                 } else {
47                     // The instance has been evicted, its SoftReference cleared.
48                     // Create and set a new instance.
49                     value = createInstance(key, data);
50                     if (value != null) {
51                         valueRef.ref = new SoftReference<V>(value);
52                     }
53                     return value;
54                 }
55             }
56         } else /* valueRef == null */ {
57             // We had never cached an instance for this key.
58             value = createInstance(key, data);
59             if (value == null) {
60                 return null;
61             }
62             valueRef = map.putIfAbsent(key, new SettableSoftReference<V>(value));
63             if(valueRef == null) {
64                 // Normal "put": Our new value is now cached.
65                 return value;
66             } else {
67                 // Race condition: Another thread beat us to putting a SettableSoftReference
68                 // into the map. Return its value, but just in case the garbage collector
69                 // was aggressive, we also offer our new instance for caching.
70                 return valueRef.setIfAbsent(value);
71             }
72         }
73     }
74     /**
75      * Value type for cache items: Has a SoftReference which can be set
76      * to a new value when the SoftReference has been cleared.
77      * The SoftCache class sometimes accesses the ref field directly.
78      *
79      * @param <V> Cache instance value type
80      */
81     private static final class SettableSoftReference<V> {
SettableSoftReference(V value)82         private SettableSoftReference(V value) {
83             ref = new SoftReference<V>(value);
84         }
85         /**
86          * If the SoftReference has been cleared, then this replaces it with a new SoftReference
87          * for the new value and returns the new value; otherwise returns the current
88          * SoftReference's value.
89          * @param value Replacement value, for when the current reference has been cleared
90          * @return The value that is held by the SoftReference, old or new
91          */
setIfAbsent(V value)92         private synchronized V setIfAbsent(V value) {
93             V oldValue = ref.get();
94             if(oldValue == null) {
95                 ref = new SoftReference<V>(value);
96                 return value;
97             } else {
98                 return oldValue;
99             }
100         }
101         private SoftReference<V> ref;  // never null
102     }
103     private ConcurrentHashMap<K, SettableSoftReference<V>> map =
104         new ConcurrentHashMap<K, SettableSoftReference<V>>();
105 }
106