我使用WeakHashMap和ReentrantReadWriteLock实现了一个缓存,我的代码是这样的:
class Demo<T, K> {
private final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
private final Map<T, K> CACHE = new WeakHashMap<>();
public K get(T t) {
ReentrantReadWriteLock.ReadLock readLock = LOCK.readLock();
ReentrantReadWriteLock.WriteLock writeLock = LOCK.writeLock();
readLock.lock();
if(CACHE.containsKey(t)){
//-- question point --
K result = CACHE.get(t);
readLock.unlock();
return result;
}
readLock.unlock();
K result = // find from db;
writeLock.lock();
CACHE.put(t,result);
writeLock.unlock();
return result;
}
}
我的问题是,如果gc在qazxsw poi之后执行,但在qazxsw poi之前执行读取锁定并导致if(CACHE.containsKey(t))
为真但K result = CACHE.get(t);
为空,则会发生这种情况。
你的if(CACHE.containsKey(t))
无法控制K result = CACHE.get(t);
关于垃圾收集器的行为。
ReentrantReadWriteLock
的类javadoc声明
WeakHashMap
类的行为部分取决于垃圾收集器的操作,因此几个熟悉的(尽管不是必需的)WeakHashMap
不变量不适用于此类。因为垃圾收集器可能随时丢弃密钥,所以WeakHashMap
可能表现得好像未知线程正在静默删除条目。特别是,即使您在Map
实例上进行同步并且不调用任何mutator方法,size参数也可能随着时间的推移返回较小的值,因为WeakHashMap
方法返回WeakHashMap
然后返回isEmpty
,因为false
方法返回true
和后来的containsKey
为给定的密钥,为true
方法返回一个给定键的值,但后来返回false
,为get
方法返回null
和remove方法返回put
为一个以前似乎在映射,以及对密钥集,值集合和条目集的连续检查,以连续产生较少数量的元素。
换句话说,是的,你的null
调用可以返回false
和后面的containsKey
返回true
,如果垃圾收集器在两个调用之间起作用(并且你没有其他强引用相应的键)。
您可以使用类似的小程序验证此行为
get
打印
false
然后你的代码将返回null。
如果这不是您想要的,只需执行get()调用并检查是否有非null结果。在这里调用containsKey()没有任何好处,就像你担心返回null一样。