I implemented a cache with using WeakHashMap and ReentrantReadWriteLock, my code is like that:
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;
}
}
My question is that whill it happen that gc is excuted after if(CACHE.containsKey(t))
but before K result = CACHE.get(t);
with read lock and lead to that if(CACHE.containsKey(t))
is true but K result = CACHE.get(t);
get null.
Your ReentrantReadWriteLock
has no control over the behavior of the WeakHashMap
with regards to the garbage collector.
The class javadoc for WeakHashMap
states
The behavior of the
WeakHashMap
class depends in part upon the actions of the garbage collector, so several familiar (though not required)Map
invariants do not hold for this class. Because the garbage collector may discard keys at any time, aWeakHashMap
may behave as though an unknown thread is silently removing entries. In particular, even if you synchronize on aWeakHashMap
instance and invoke none of its mutator methods, it is possible for the size method to return smaller values over time, for theisEmpty
method to returnfalse
and thentrue
, for thecontainsKey
method to returntrue
and laterfalse
for a given key, for theget
method to return a value for a given key but later returnnull
, for theput
method to returnnull
and the remove method to returnfalse
for a key that previously appeared to be in the map, and for successive examinations of the key set, the value collection, and the entry set to yield successively smaller numbers of elements.
In other words, yes, it's possible for your containsKey
call to return true
and the get
that follows to return false
, if the garbage collector acts in between the two calls (and you have no other strong references to the corresponding key).
You can verify this behavior with a small program like
public class Example {
public static void main(String[] args) throws Exception {
WeakHashMap<Example, Integer> CACHE = new WeakHashMap<>();
CACHE.put(new Example(), 2);
if (CACHE.containsKey(new Example())) {
System.gc();
System.out.println("missing? " + CACHE.get(new Example()));
}
}
@Override
public int hashCode() {
return 42;
}
@Override
public boolean equals(Object obj) {
return true;
}
}
which prints
missing? null