4

我最近遇到了以下构造

Map<String,Value> map = new HashMap<>();
...
Value getValue(String key) {
    synchronized (key.intern()) {
        return map.remove(key);
    }
}

鉴于这intern()通常不是那么快,我怀疑这会优于使用synchronized,Collections.synchronizedMap(Map)ConcurrentHashMap. 但是,即使在这种特殊情况下,这种构造会比所有其他方法更快:这是否正确同步?我怀疑这是线程安全的,因为在重新组织哈希表时可能会发生删除。但即使这可行,我怀疑鉴于HashMap javadoc状态代码会被破坏:

如果多个线程同时访问一个哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。

4

1 回答 1

7

这不足以安全地HashMap从多个线程访问 a。事实上,它几乎肯定会破坏某些东西。通过在给定键上同步,地图仍然可以不安全地同时修改,只要单独的线程使用不同的键。

考虑这三个线程是否试图同时运行:

Thread 1                Thread 2                 Thread 3
synchronized("a") {     synchronized("a") {      synchronized("b") {
  map.remove("a");        map.remove("a");         map.remove("b");
}                       }                        }

线程 1 和 2 将正确地相互等待,因为它们在同一个对象上同步(Java 实习生字符串常量)。但是线程 3 不受其他线程中正在进行的工作的阻碍,并立即进入其同步块,因为没有其他线程正在锁定"b"。现在两个不同的同步块同时交互map,所有赌注都关闭了。不久,你的HashMap意志就会腐败。

Collections.synchronizedMap()正确使用地图本身作为同步对象,因此锁定整个地图,而不仅仅是正在使用的键。这是防止HashMap从多个线程访问的内部损坏的唯一可靠方法。

ConcurrentHashMap正确地做我认为你发布的代码试图通过在内部锁定地图中所有键的子集来做的事情。这样,多个线程可以安全地访问不同线程上的不同键,而不会相互阻塞——但如果键碰巧在同一个桶中,映射仍然会阻塞。concurrencyLevel您可以使用构造函数参数修改此行为。

另请参阅:Java 同步块与 Collections.synchronizedMap


顺便说一句,为了论证的缘故,我们假设这synchronized(key.intern()) 一种同时访问 a 的合理方式HashMap。这仍然非常容易出错。如果应用程序中只有一个地方未能调用.intern()某个键,那么一切都可能崩溃。

于 2014-12-09T23:21:51.103 回答