首页 > 解决方案 > Java SoftReference 奇怪的行为

问题描述

Map<E, SoftReference<T>> cache = new ConcurrentHashMap<E, SoftReference<T>>();

我已经 map 声明了一个类似于上面的地图,我将其用作缓存。

问题是我要在将项目添加到缓存后立即在缓存上执行所有操作,而不是稍后。

例如:

cache.add("Username", "Tom");

if(cache.contains("Username"))返回 true 但

String userName = (String)cache.get("Username")返回空值。

这只有在很长一段时间后才会发生。

如果我在将它添加到缓存几个小时后得到了这个值,我就得到了正确的值。如果我在很长一段时间后得到这个值,比如超过 15-20 小时,我会得到空值。

GC清除SoftReference对象时,key会留在HashMap中吗?这是这种行为的原因吗?

标签: javagarbage-collectionsoft-references

解决方案


当 a 的引用SoftReference被垃圾回收时,SoftReference清除,即它的引用字段被设置为null

因此,不仅 key 保留在 map 中,关联的 valueSoftReference实例也保留在 map 中,即使它的引用字段是null.

Map<E, SoftReference<T>>但是,由于在您声明的字段和 and 的调用者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


推荐阅读