Map<E, SoftReference<T>> cache = new ConcurrentHashMap<E, SoftReference<T>>();
我已将地图声明为与我用作缓存的上述地图相同的地图。
问题是,我要在将项目添加到缓存后立即对缓存执行所有操作,但是以后不能执行。
例如:
cache.add("Username", "Tom");
if(cache.contains("Username"))
返回true,但是
String userName = (String)cache.get("Username")
返回null。
这种情况仅在很长时间之后才会发生。
如果在将其添加到缓存几个小时后得到该值,则可以正确获取该值。 如果经过很长时间(例如15到20个小时以上)后才获得该值,那么我将得到null。
当GC清除SoftReference对象时,密钥是否仍保留在HashMap中?这是这种行为的原因吗?
答案 0 :(得分:2)
当SoftReference
的引用对象被垃圾回收时,SoftReference
被清除 ,即其引用字段设置为null
。
因此,键不仅保留在映射中,而且相关值SoftReference
实例也保留在映射中,即使其引用字段为null
。
但是由于您声明的Map<E, SoftReference<T>>
字段与cache.add("Username", "Tom")
和(String)cache.get("Username")
的调用者之间必须存在一层(您没有显示),所以甚至可能出现这种情况层正确处理。
为完整起见,正确的实现可能看起来像
final Map<E, SoftReference<T>> cache = new ConcurrentHashMap<>();
/** remove all cleared references */
private void clean() {
cache.values().removeIf(r -> r.get() == null);
}
public void add(E key, T value) {
clean();
cache.put(key, new SoftReference<>(value));
}
public boolean contains(E key) {
clean();
return cache.computeIfPresent(key, (e,r) -> r.get()==null? null: r) != null;
}
public T get(E key) {
clean();
for(;;) {
SoftReference<T> ref = cache.computeIfPresent(key, (e,r) -> r.get()==null? null: r);
if(ref == null) return null;
T value = ref.get();
if(value != null) return value;
}
}
此代码可确保在查询时自动删除所收集值的映射。另外,尽管不是必需的,clean()
操作会删除所有收集的地图条目,以减少地图的空间(与WeakHashMap
在内部的工作方式相比)。
但是请注意,仍然没有保证,当cache.contains("Username")
返回true
时,随后的cache.get("Username")
将返回非null
值。此问题也称为 check-then-act 反模式,它可能会失败,因为在检查和后续操作之间可能会发生并发更新(即使您正在使用缓存, (因为垃圾回收可能异步发生),因此仅落后于一个测试。
在这方面,contains
操作在大多数情况下是无用的。您必须调用get
,以接收对值的强引用,如果非null
,则必须继续使用该引用。
答案 1 :(得分:1)
在虚拟机引发OutOfMemoryError之前,确保已清除所有对软可访问对象的软引用。
是当GC清除SoftReference对象时,密钥保留在HashMap中。键和对应的值除了在地图中时没有其他关系。将map's value
用作常规参考,除非map是GC,否则它们将始终在map中。
答案 2 :(得分:0)
是的,这是正常的行为。
SoftReference是垃圾收集的,导致Map中的值设置为 null 。
与将其他类型的地图(例如Map)的某个键的值设置为null相同。