ICU-7434 ICU4J SoftCache with CacheValue that can be null (which were not cached before) or hold a direct reference or hold a resettable Reference; CacheValue strength can be set for cache warm-up
X-SVN-Rev: 38734
This commit is contained in:
parent
3f27a9e89d
commit
37c12c5961
148
icu4j/main/classes/core/src/com/ibm/icu/impl/CacheValue.java
Normal file
148
icu4j/main/classes/core/src/com/ibm/icu/impl/CacheValue.java
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2016, International Business Machines Corporation and
|
||||
* others. All Rights Reserved.
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.impl;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.SoftReference;
|
||||
|
||||
import com.ibm.icu.util.ICUException;
|
||||
|
||||
/**
|
||||
* Value type for cache items:
|
||||
* Holds a value either via a direct reference or via a {@link Reference},
|
||||
* depending on the current "strength" when {@code getInstance()} was called.
|
||||
*
|
||||
* <p>The value is <i>conceptually<i> immutable.
|
||||
* If it is held via a direct reference, then it is actually immutable.
|
||||
*
|
||||
* <p>A {@code Reference} may be cleared (garbage-collected),
|
||||
* after which {@code get()} returns null.
|
||||
* It can then be reset via {@code resetIfAbsent()}.
|
||||
* The new value should be the same as, or equivalent to, the old value.
|
||||
*
|
||||
* <p>Null values are supported. They can be distinguished from cleared values
|
||||
* via {@code isNull()}.
|
||||
*
|
||||
* @param <V> Cache instance value type
|
||||
*/
|
||||
public abstract class CacheValue<V> {
|
||||
/**
|
||||
* "Strength" of holding a value in CacheValue instances.
|
||||
* The default strength is {@code SOFT}.
|
||||
*/
|
||||
public enum Strength {
|
||||
/**
|
||||
* Subsequent {@code getInstance()}-created objects
|
||||
* will hold direct references to their values.
|
||||
*/
|
||||
STRONG,
|
||||
/**
|
||||
* Subsequent {@code getInstance()}-created objects
|
||||
* will hold {@link SoftReference}s to their values.
|
||||
*/
|
||||
SOFT
|
||||
};
|
||||
private static volatile Strength strength = Strength.SOFT;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final CacheValue NULL_VALUE = new NullValue();
|
||||
|
||||
/**
|
||||
* Changes the "strength" of value references for subsequent {@code getInstance()} calls.
|
||||
*/
|
||||
public static void setStrength(Strength strength) { CacheValue.strength = strength; }
|
||||
|
||||
/**
|
||||
* Returns true if the "strength" is set to {@code STRONG}.
|
||||
*/
|
||||
public static boolean futureInstancesWillBeStrong() { return strength == Strength.STRONG; }
|
||||
|
||||
/**
|
||||
* Returns a CacheValue instance that holds the value.
|
||||
* It holds it directly if the value is null or if the current "strength" is {@code STRONG}.
|
||||
* Otherwise, it holds it via a {@link Reference}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <V> CacheValue<V> getInstance(V value) {
|
||||
if (value == null) {
|
||||
return NULL_VALUE;
|
||||
}
|
||||
return strength == Strength.STRONG ? new StrongValue<V>(value) : new SoftValue<V>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Distinguishes a null value from a Reference value that has been cleared.
|
||||
*
|
||||
* @return true if this object represents a null value.
|
||||
*/
|
||||
public boolean isNull() { return false; }
|
||||
/**
|
||||
* Returns the value (which can be null),
|
||||
* or null if it was held in a Reference and has been cleared.
|
||||
*/
|
||||
public abstract V get();
|
||||
/**
|
||||
* If the value was held via a {@link Reference} which has been cleared,
|
||||
* then it is replaced with a new {@link Reference} to the new value,
|
||||
* and the new value is returned.
|
||||
* The old and new values should be the same or equivalent.
|
||||
*
|
||||
* <p>Otherwise the old value is returned.
|
||||
*
|
||||
* @param value Replacement value, for when the current {@link Reference} has been cleared.
|
||||
* @return The old or new value.
|
||||
*/
|
||||
public abstract V resetIfCleared(V value);
|
||||
|
||||
private static final class NullValue<V> extends CacheValue<V> {
|
||||
@Override
|
||||
public boolean isNull() { return true; }
|
||||
@Override
|
||||
public V get() { return null; }
|
||||
@Override
|
||||
public V resetIfCleared(V value) {
|
||||
if (value != null) {
|
||||
throw new ICUException("resetting a null value to a non-null value");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StrongValue<V> extends CacheValue<V> {
|
||||
private V value;
|
||||
|
||||
StrongValue(V value) { this.value = value; }
|
||||
@Override
|
||||
public V get() { return value; }
|
||||
@Override
|
||||
public V resetIfCleared(V value) {
|
||||
// value and this.value should be equivalent, but
|
||||
// we do not require equals() to be implemented appropriately.
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SoftValue<V> extends CacheValue<V> {
|
||||
private Reference<V> ref;
|
||||
|
||||
SoftValue(V value) { ref = new SoftReference<V>(value); }
|
||||
@Override
|
||||
public V get() { return ref.get(); }
|
||||
@Override
|
||||
public synchronized V resetIfCleared(V value) {
|
||||
V oldValue = ref.get();
|
||||
if (oldValue == null) {
|
||||
ref = new SoftReference<V>(value);
|
||||
return value;
|
||||
} else {
|
||||
// value and oldValue should be equivalent, but
|
||||
// we do not require equals() to be implemented appropriately.
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -102,7 +102,8 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
|
||||
super(container, key, resource);
|
||||
String s = wholeBundle.reader.getString(resource);
|
||||
// Allow the reader cache's SoftReference to do its job.
|
||||
if (s.length() < ICUResourceBundleReader.LARGE_SIZE / 2) {
|
||||
if (s.length() < ICUResourceBundleReader.LARGE_SIZE / 2 ||
|
||||
CacheValue.futureInstancesWillBeStrong()) {
|
||||
value = s;
|
||||
}
|
||||
}
|
||||
|
@ -1182,7 +1182,7 @@ public final class ICUResourceBundleReader {
|
||||
* <p>This cache uses int[] and Object[] arrays to minimize object creation
|
||||
* and avoid auto-boxing.
|
||||
*
|
||||
* <p>Large resource objects are stored in SoftReferences.
|
||||
* <p>Large resource objects are usually stored in SoftReferences.
|
||||
*
|
||||
* <p>For few resources, a small table is used with binary search.
|
||||
* When more resources are cached, then the data structure changes to be faster
|
||||
@ -1210,11 +1210,18 @@ public final class ICUResourceBundleReader {
|
||||
private int levelBitsList;
|
||||
private Level rootLevel;
|
||||
|
||||
private static boolean storeDirectly(int size) {
|
||||
return size < LARGE_SIZE || CacheValue.futureInstancesWillBeStrong();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Object putIfCleared(Object[] values, int index, Object item, int size) {
|
||||
Object value = values[index];
|
||||
if(!(value instanceof SoftReference)) {
|
||||
assert size < LARGE_SIZE; // Caller should be consistent for each resource.
|
||||
// The caller should be consistent for each resource,
|
||||
// that is, create equivalent objects of equal size every time,
|
||||
// but the CacheValue "strength" may change over time.
|
||||
// assert size < LARGE_SIZE;
|
||||
return value;
|
||||
}
|
||||
assert size >= LARGE_SIZE;
|
||||
@ -1222,7 +1229,8 @@ public final class ICUResourceBundleReader {
|
||||
if(value != null) {
|
||||
return value;
|
||||
}
|
||||
values[index] = new SoftReference<Object>(item);
|
||||
values[index] = CacheValue.futureInstancesWillBeStrong() ?
|
||||
item : new SoftReference<Object>(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
@ -1271,7 +1279,7 @@ public final class ICUResourceBundleReader {
|
||||
return level.putIfAbsent(key, item, size);
|
||||
}
|
||||
keys[index] = key;
|
||||
values[index] = (size >= LARGE_SIZE) ? new SoftReference<Object>(item) : item;
|
||||
values[index] = storeDirectly(size) ? item : new SoftReference<Object>(item);
|
||||
return item;
|
||||
}
|
||||
// Collision: Add a child level, move the old item there,
|
||||
@ -1403,7 +1411,7 @@ public final class ICUResourceBundleReader {
|
||||
}
|
||||
++length;
|
||||
keys[index] = res;
|
||||
values[index] = (size >= LARGE_SIZE) ? new SoftReference<Object>(item) : item;
|
||||
values[index] = storeDirectly(size) ? item : new SoftReference<Object>(item);
|
||||
return item;
|
||||
} else /* not found && length == SIMPLE_LENGTH */ {
|
||||
// Grow to become trie-like.
|
||||
|
@ -1,105 +1,86 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2010-2011, International Business Machines
|
||||
* Copyright (C) 2010-2016, 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.
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* <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
|
||||
* @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 on the map and once on valueRef,
|
||||
// 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 SettableSoftReference (a second level of indirection) because
|
||||
// We use a CacheValue (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.
|
||||
// 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.)
|
||||
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;
|
||||
}
|
||||
Object mapValue = map.get(key);
|
||||
if(mapValue != null) {
|
||||
if(!(mapValue instanceof CacheValue)) {
|
||||
// The value was stored directly.
|
||||
return (V)mapValue;
|
||||
}
|
||||
} else /* valueRef == null */ {
|
||||
// We had never cached an instance for this key.
|
||||
value = createInstance(key, data);
|
||||
if (value == null) {
|
||||
CacheValue<V> cv = (CacheValue<V>)mapValue;
|
||||
if(cv.isNull()) {
|
||||
return null;
|
||||
}
|
||||
valueRef = map.putIfAbsent(key, new SettableSoftReference<V>(value));
|
||||
if(valueRef == 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;
|
||||
} 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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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>>();
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,67 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2016, International Business Machines Corporation and
|
||||
* others. All Rights Reserved.
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.dev.test.impl;
|
||||
|
||||
import com.ibm.icu.dev.test.TestFmwk;
|
||||
import com.ibm.icu.impl.CacheValue;
|
||||
import com.ibm.icu.impl.CacheValue.Strength;
|
||||
|
||||
public class CacheTest extends TestFmwk {
|
||||
public CacheTest() {}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new CacheTest().run(args);
|
||||
}
|
||||
|
||||
/** Code coverage for CacheValue. */
|
||||
public void testNullCacheValue() {
|
||||
CacheValue<Object> nv = CacheValue.getInstance(null);
|
||||
assertTrue("null CacheValue isNull()", nv.isNull());
|
||||
assertTrue("null CacheValue get()==null", nv.get() == null);
|
||||
assertTrue("null CacheValue reset==null", nv.resetIfCleared(null) == null);
|
||||
try {
|
||||
Object v = nv.resetIfCleared(this);
|
||||
fail("null CacheValue reset(not null) should throw an Exception, returned " +
|
||||
v + " instead");
|
||||
} catch(Exception expected) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Code coverage for CacheValue. */
|
||||
public void testStrongCacheValue() {
|
||||
boolean wasStrong = CacheValue.futureInstancesWillBeStrong();
|
||||
CacheValue.setStrength(Strength.STRONG);
|
||||
assertTrue("setStrength(STRONG).futureInstancesWillBeStrong()",
|
||||
CacheValue.futureInstancesWillBeStrong());
|
||||
CacheValue<Object> sv = CacheValue.<Object>getInstance(this);
|
||||
assertFalse("strong CacheValue not isNull()", sv.isNull());
|
||||
assertTrue("strong CacheValue get()==same", sv.get() == this);
|
||||
// A strong CacheValue never changes value.
|
||||
// The implementation does not check that the new value is equal to the old one,
|
||||
// or even of equal type, so it does not matter which new value we pass in.
|
||||
assertTrue("strong CacheValue reset==same", sv.resetIfCleared("") == this);
|
||||
if (!wasStrong) {
|
||||
CacheValue.setStrength(Strength.SOFT);
|
||||
}
|
||||
}
|
||||
|
||||
/** Code coverage for CacheValue. */
|
||||
public void testSoftCacheValue() {
|
||||
boolean wasStrong = CacheValue.futureInstancesWillBeStrong();
|
||||
CacheValue.setStrength(Strength.SOFT);
|
||||
assertFalse("setStrength(SOFT).futureInstancesWillBeStrong()",
|
||||
CacheValue.futureInstancesWillBeStrong());
|
||||
CacheValue<Object> sv = CacheValue.<Object>getInstance(this);
|
||||
assertFalse("soft CacheValue not isNull()", sv.isNull());
|
||||
Object v = sv.get();
|
||||
assertTrue("soft CacheValue get()==same or null", v == this || v == null);
|
||||
assertTrue("soft CacheValue reset==same", sv.resetIfCleared(this) == this);
|
||||
if (wasStrong) {
|
||||
CacheValue.setStrength(Strength.STRONG);
|
||||
}
|
||||
}
|
||||
}
|
@ -17,18 +17,12 @@ public class TestAll extends TestGroup {
|
||||
}
|
||||
|
||||
public TestAll() {
|
||||
super("com.ibm.icu.dev.test.util",
|
||||
new String[] {
|
||||
"ICUServiceTest",
|
||||
"ICUServiceThreadTest",
|
||||
"ICUBinaryTest",
|
||||
"SimpleFormatterTest",
|
||||
"TextTrieMapTest"
|
||||
},
|
||||
"Test miscellaneous implementation utilities");
|
||||
super(
|
||||
new String[] {
|
||||
"CacheTest"
|
||||
},
|
||||
"Test miscellaneous implementation classes");
|
||||
}
|
||||
|
||||
public static final String CLASS_TARGET_NAME = "Impl";
|
||||
}
|
||||
|
||||
|
||||
|
@ -19,6 +19,11 @@ public class TestAll extends TestGroup {
|
||||
public TestAll() {
|
||||
super(
|
||||
new String[] {
|
||||
"ICUServiceTest",
|
||||
"ICUServiceThreadTest",
|
||||
"ICUBinaryTest",
|
||||
"SimpleFormatterTest",
|
||||
"TextTrieMapTest",
|
||||
"VersionInfoTest",
|
||||
"ICUResourceBundleTest",
|
||||
"BytesTrieTest",
|
||||
|
Loading…
Reference in New Issue
Block a user