前言(个人的理解):缓存 一般用来缓解耗时、耗资源的问题,而本地缓存(jvm缓存),相对于需要通过网络连接来访问的缓存(如Redis),本地缓存主要用来缓解耗时问题,以及本地缓存实现起来比较方便 而远程缓存支持存储的对象不够完善(如需要通过序列化/反序列化来解决,本质来说 还是时间问题)。
关于Reference所引用对象的生存时间:
WeakReference<T>:如果当前不存在强引用指向对象T,则对象T的生存时间是一轮GC内;
SoftReference<T>:如果当前不存在强引用指向对象T,则对象T的生存时间直到系统将要发生内存溢出时才会被清除(一轮Full GC的时间?);
因此 使用哪种类型的缓存 看自己的业务需求了。
注:以下实现 模仿自java.lang.reflect.WeakCache。
首先是基类,封装了数据存取逻辑,子类只需提供具体类型的Reference即可。与直接采用WeakReference/SoftReference的区别,该实现是key-value的形式(其中key是强引用,value才是弱引用/软引用),因此使用场景是 需要根据类别缓存多个同一类别的情况。
import java.lang.ref.ReferenceQueue;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Cache mapping a {@code key -> value}. values are weakly(or softly) but keys are strongly referenced.
* Keys are passed directly to {@link #get} method.Values are calculated from keys using the {@code valueFactory}
* function passed to the constructor. Keys can not be {@code null} and are compared by equals
* while values returned by {@code valueFactory} can be null and are compared by identity.
* Entries are expunged from cache lazily on invocation to {@link #get} method when the WeakReferences(or SoftReference)
* to values are cleared.
* <p>this class is imitate from java.lang.reflect.WeakCache
*
* @param <K> type of keys
* @param <V> type of values
*/
public abstract class ReferenceCache<K, V> {
private static Log log ;
private final ReferenceQueue<V> refQueue
= new ReferenceQueue<>();
private final ConcurrentMap<K,Value<K, V>> map
= new ConcurrentHashMap<>();
private final Function<K, V> valueFactory;
/**
* Construct an instance of {@code ReferenceCache}
*
* @param valueFactory a function mapping a {@code key -> value}
* @throws NullPointerException if {@code valueFactory} is null.
*/
protected ReferenceCache(Function<K, V> valueFactory){
this.valueFactory = Objects.requireNonNull(valueFactory);
}
private static Log getLog(){
// lazily init the log
if(log==null){
// regardless of the concurrency
log = LogFactory.getLog(ReferenceCache.class);
}
return log;
}
/**
* Look-up the value through the cache.
*
* @param key
* @return the cached value (maybe null)
* @throws NullPointerException if {@code key} passed in is null.
*/
public final V get(K key){
Objects.requireNonNull(key);
expungeStaleEntries();
Value<K,V> cache = map.get(key);
Value<K,V> newCache = null;
while(true){
if(cache!=null){
V value = cache.get();
if(value!=null){
return value;
}
}
// lazily construct a new-CacheEntry
if(newCache==null){
// create new value
V value = valueFactory.apply(key);
// if new-value is null then just return it
if(value==null){
return null;
}
// wrap value with CacheValue (WeakReference or SoftReference)
newCache = createNewValue(key, value, refQueue);
}
if(cache==null){
cache = map.putIfAbsent(key, newCache);
if(cache==null){
// successfully put new-cache
cache = newCache;
}
}else{
if(map.replace(key, cache, newCache)){
if(cache==newCache){
//newCache is cleared?
getLog().error("should not reach here!---->there is a bug in ReferenceCache? newCache.value="
+ newCache.get()+" -->"+newCache.getClass());
return valueFactory.apply(key);
}
// successfully replaced cleared CacheEntry with our new-CacheEntry
cache = newCache;
}else{
// retry with current cache-value
cache = map.get(key);
}
}
}
}
/**
* expunge all stale cache entries
*/
private void expungeStaleEntries(){
Value<K,V> cache;
while ((cache = (Value<K,V>)refQueue.poll()) != null){
// removing by key and mapped value is always safe here because after a Value
// is cleared and enqueue-ed it is only equal to itself
// (see Value.equals method)...
map.remove(cache.getKey(), cache);
}
}
/**
* Removes all of the mappings from this cache.
* The cache will be empty after this call returns.
*/
public final void clear(){
// to remove from refQueue
while (refQueue.poll() != null)
;
map.clear();
}
/**
* Removes the key (and its corresponding value) from this cache.
* This method does nothing if the key is not in the cache.
*
* @param key the key that needs to be removed
* @return the previous value associated with {@code key}, or
* {@code null} if there was no mapping for {@code key}
* ( or already cleared)
* @throws NullPointerException if the specified key is null
*/
public final V remove(Object key){
expungeStaleEntries();// to remove from refQueue
Value<K, V> val = map.remove(key);
if(val!=null){
return val.get();
}
return null;
}
/**
* Constructs a new {@code Value} (WeakReference or SoftReference)
* with specified key, value and ReferenceQueue.
* @param key the key
* @param value the value
* @param refQueue the ReferenceQueue
* @return a new Value
*/
protected abstract Value<K,V> newValue(K key, V value, ReferenceQueue<V> refQueue);
/**
* Common type of value suppliers that are holding a referent.
* The {@link #equals} and {@link #hashCode} of implementations is defined
* to compare the referent by identity and cleared Value is only equal to itself.
*
* @param <K> type of keys
* @param <V> type of values
*/
protected static interface Value<K,V> extends Supplier<V> {
/**
* Gets the key.
*
* @return key
*/
K getKey();
}
}
软引用SoftReference的实现:
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Objects;
import java.util.function.Function;
/**
* Cache mapping a {@code key -> value}. values are softly but keys are strongly referenced.
* Keys are passed directly to {@link #get} method.Values are calculated from keys using the {@code valueFactory}
* function passed to the constructor. Keys can not be {@code null} and are compared by equals
* while values returned by {@code valueFactory} can be null and are compared by identity.
* Entries are expunged from cache lazily on invocation to {@link #get} method when the SoftReference to
* values are cleared.
*
* @param <K> type of keys
* @param <V> type of values
*/
public final class SoftCache<K, V> extends ReferenceCache<K, V>{
/**
* Construct an instance of {@code SoftCache}
*
* @param valueFactory a function mapping a {@code key -> value}
* @throws NullPointerException if {@code valueFactory} is null.
*/
public SoftCache(Function<K, V> valueFactory) {
super(valueFactory);
}
/**
* create a new instance of Value(SoftReference)
*/
@Override
protected Value<K, V> newValue(
K key, V value, ReferenceQueue<V> refQueue) {
return new CacheValue<K, V>(key, value, refQueue);
}
/**
* CacheValue containing a softly referenced {@code value}. It registers
* itself with the {@code refQueue} so that it can be used to expunge
* the entry when the {@link SoftReference} is cleared.
*/
private static final class CacheValue<K, V> extends SoftReference<V> implements Value<K, V> {
private final int hash;
private final K key;
private CacheValue(K key, V value, ReferenceQueue<V> refQueue) {
super(value, refQueue);
this.hash = System.identityHashCode(value); // compare by identity
this.key = Objects.requireNonNull(key);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
V value;
return obj == this ||
obj != null &&
obj.getClass() == this.getClass() &&
// cleared CacheValue is only equal to itself
(value = this.get()) != null &&
// compare value by identity
value == ((CacheValue<K, V>) obj).get();
}
public K getKey() {
return key;
}
}
}
弱引用WeakReference的实现:
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.function.Function;
/**
* Cache mapping a {@code key -> value}. values are weakly but keys are strongly referenced.
* Keys are passed directly to {@link #get} method.Values are calculated from keys using the {@code valueFactory}
* function passed to the constructor. Keys can not be {@code null} and are compared by equals
* while values returned by {@code valueFactory} can be null and are compared by identity.
* Entries are expunged from cache lazily on invocation to {@link #get} method when the WeakReferences to
* values are cleared.
*
* @param <K> type of keys
* @param <V> type of values
*/