如何使用ReadWriteLock?

时间:2009-10-29 12:38:52

标签: java caching map

我是以下情况。

在Web应用程序启动时,我需要加载一个Map,然后由多个传入线程使用。也就是说,请求进入并且Map用于查明它是否包含特定键,如果是,则检索值(对象)并将其与另一个对象关联。

现在,地图的内容有时会发生变化。我不想重新启动我的应用程序来重新加载新的情况。相反,我想动态地这样做。

但是,在Map重新加载(删除所有项目并用新项目替换它们)时,该地图上的并发读取请求仍然到达。

如何防止所有读取线程在重新加载时访问该地图?我怎样才能以最高性能的方式做到这一点,因为我只需要在Map重新加载时只需偶尔发生(每x周一次)?

如果以上不是一个选项(阻止),我怎样才能确保在重新加载我的读取请求时不会遇到意外的异常(因为某个键不再存在,或者某个值不再存在或正在重新加载)?

我得到了ReadWriteLock可能帮助我的建议。你能否为我提供一个关于如何将这个ReadWriteLock与我的读者和作者一起使用的例子?

谢谢,
ë

5 个答案:

答案 0 :(得分:6)

我建议按照以下方式处理:

  1. 让您的地图可以在中心位置访问(可以是Spring单例,静态...)。
  2. 开始重新加载时,让实例保持原样,在另一个Map实例中工作。
  3. 当填充新地图时,用新的地图替换旧地图(这是原子操作)。
  4. 示例代码:

        static volatile Map<U, V> map = ....;
    
        // **************************
    
        Map<U, V> tempMap = new ...;
        load(tempMap);
        map = tempMap;
    

    并发效果:

    • volatile有助于将变量的可见性提供给其他线程。
    • 在重新加载地图时,所有其他线程都会看到旧值不受干扰,因此不会受到任何惩罚。
    • 任何在更改映射之前检索映射的线程都将使用旧值。
      • 它可以要求多个获取相同的旧地图实例,这对于数据一致性很好(不加载旧地图中的第一个值,而不是来自较新地图的其他值)。
      • 它将使用旧地图完成处理其请求,但下一个请求将再次询问地图,并将收到更新的值。

答案 1 :(得分:3)

如果客户端线程不修改映射,即映射的内容完全依赖于加载映射的源,您只需加载新映射并替换对客户端线程使用的映射的引用加载新地图后。

然后在短时间内使用两倍内存,不会造成性能损失。

如果地图使用太多内存来拥有其中的2个,则可以在地图中使用相同的每个对象策略;迭代地图,构造一个新的映射到对象,并在加载对象后替换原始映射。

答案 2 :(得分:2)

请注意,如果您依赖地图一段时间不变(例如if (map.contains(key)) {V value = map.get(key); ...}),更改其他人建议的引用可能会导致问题。如果您需要,您应该保留对地图的本地引用:< / p>

static Map<U,V> map = ...;

void do() {
  Map<U,V> local = map;
  if (local.contains(key)) {
    V value = local.get(key);
    ...
  }
}

编辑:

假设您不希望客户端线程进行昂贵的同步。作为权衡,您允许客户端线程完成他们在地图更改之前已经开始的工作 - 忽略对运行时发生的地图的任何更改。通过这种方式,您可以安全地对地图做出一些假设 - 例如密钥存在并始终在单个请求的持续时间内映射到相同的值。在上面的示例中,如果您的读者线程在名为map.contains(key)的客户端之后更改了映射,则客户端可能在map.get(key)上变为空 - 并且您几乎肯定会使用NullPointerException结束此请求。因此,如果您正在对地图进行多次读取并需要像前面提到的那样做一些假设,那么最简单的方法是保留对(可能是过时的)地图的本地引用。

此处不一定非常需要volatile关键字。只要您更改引用(map = newMap),它就会确保其他线程使用新映射。如果没有volatile,后续的读取(local = map)仍然可以返回旧的引用一段时间(我们谈论的不到一个纳秒) - 特别是在多核系统上,如果我没记错的话。我不会在乎它,但如果您觉得需要额外的多线程美容,当然可以免费使用它;)

答案 3 :(得分:2)

我非常喜欢来自KLE的易变地图解决方案,并且会继续使用它。有人可能会感兴趣的另一个想法是使用类似于CopyOnWriteArrayList的地图,基本上是CopyOnWriteMap。我们在内部构建了其中一个并且它非常简单,但您可以在野外找到COWMap:

http://old.nabble.com/CopyOnWriteMap-implementation-td13018855.html

答案 4 :(得分:1)

这是ReentrantReadWriteLock implementation of ReadWriteLock的JDK javadocs的答案。迟了几年但仍然有效,特别是如果你不想只依赖volatile

class RWDictionary {
    private final Map<String, Data> m = new TreeMap<String, Data>();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Data get(String key) {
        r.lock();
        try { return m.get(key); }
        finally { r.unlock(); }
    }
    public String[] allKeys() {
        r.lock();
        try { return m.keySet().toArray(); }
        finally { r.unlock(); }
    }
    public Data put(String key, Data value) {
        w.lock();
        try { return m.put(key, value); }
        finally { w.unlock(); }
    }
    public void clear() {
        w.lock();
        try { m.clear(); }
        finally { w.unlock(); }
    }
}