【问题标题】:WeakHashMap and ReentrantReadWriteLockWeakHashMap 和 ReentrantReadWriteLock
【发布时间】:2019-04-09 03:39:48
【问题描述】:

我使用 Wea​​kHashMap 和 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 在if(CACHE.containsKey(t)) 之后但在K result = CACHE.get(t); 之前执行读锁并导致if(CACHE.containsKey(t)) 为真但K result = CACHE.get(t); 为空。

【问题讨论】:

  • 作为一个侧节点,你使用锁定的方式将来会给你带来问题(例如,解锁两次,没有安全措施)。在 lock 类的 javadoc 中是一个如何安全使用锁的示例(通过使用 try-finally 块)。
  • 只是不要使用这种双重查找。只需使用K result = CACHE.get(t); if(result != null) return result;。问题解决了。除此之外,您正在释放读锁,然后获取写锁;如果另一个线程在这两个步骤之间设置了一个值,您将覆盖映射。一旦获得写锁,您必须重新检查。考虑putIfAbsent(如果您使用的是 Java 8)。
  • @Holger 非常感谢您的建议,这对我很有用。

标签: java garbage-collection jvm reentrantreadwritelock


【解决方案1】:

您的ReentrantReadWriteLock 无法控制WeakHashMap 在垃圾收集器方面的行为。

WeakHashMap 状态的类 javadoc

WeakHashMap 类的行为部分取决于操作 垃圾收集器,所以几个熟悉的(虽然不是必需的) Map 不变量不适用于此类。因为垃圾 收集器可以随时丢弃密钥,WeakHashMap 可能表现为 尽管一个未知的线程正在默默地删除条目。尤其, 即使您在 WeakHashMap 实例上同步并且不调用任何 它的 mutator 方法,size 方法有可能返回 随着时间的推移更小的值,isEmpty 方法返回 false 和 然后true对于containsKey 方法返回true 和后来的false 对于给定键get 方法返回给定键的值 但稍后返回null,对于put 方法返回null 和 删除方法以返回 false 的密钥,该密钥以前似乎是 在地图中,对于键集的连续检查,值 集合,并且条目设置为连续产生较小数量的 元素。

换句话说,是的,如果垃圾收集器在两个调用之间起作用(并且您没有其他对相应键的强引用)。

您可以使用类似的小程序验证此行为

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;
    }
}

打印出来的

missing? null

【讨论】:

    【解决方案2】:

    那么你的代码将返回 null。

    如果这不是您想要的,只需执行 get() 调用并检查您是否得到非空结果。如果您担心返回 null,那么在这里调用 containsKey() 没有任何好处。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-08
      • 2012-02-28
      • 2014-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多