编程语言
首页 > 编程语言> > java – HashMap.remove()通过在Key上使用String.intern()同步,这甚至可以工作吗?或者这是破碎的代码?

java – HashMap.remove()通过在Key上使用String.intern()同步,这甚至可以工作吗?或者这是破碎的代码?

作者:互联网

我最近遇到了以下构造

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

鉴于intern() is usually not that fast,我怀疑使用synchronized,Collections.synchronizedMap(Map)或ConcurrentHashMap会胜过这个.但即使这个构造比这个特殊情况下的所有其他方法更快:这是否正确同步?我怀疑这是线程安全的,因为在重组哈希表时可能会发生删除.但即使这样可行,我怀疑在HashMap javadoc规定的情况下代码会被破坏:

If multiple threads access a hash map concurrently, and at least one
of the threads modifies the map structurally, it must be synchronized
externally.

解决方法:

这不足以从多个线程安全地访问HashMap.事实上,它几乎可以保证打破某些东西.通过同步给定键,只要单独的线程使用不同的键,就可以同时不安全地修改映射.

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

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”.现在两个不同的同步块同时与地图交互,所有投注都关闭.不久,你的HashMap将会被破坏.

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

ConcurrentHashMap通过在内部锁定地图中所有键的子集来正确地执行我认为您发布的代码正在尝试执行的操作.这样,多个线程可以安全地访问不同线程上的不同密钥,并且永远不会相互阻塞 – 但如果密钥恰好位于同一个存储桶中,则映射仍将阻塞.您可以使用concurrencyLevel构造函数参数修改此行为.

另见:Java synchronized block vs. Collections.synchronizedMap

顺便说一句,让我们假设为了参数,synchronized(key.intern())是一种同时访问HashMap的合理方法.这仍然会非常容易出错.如果应用程序中只有一个地方无法在密钥上调用.intern(),那么一切都可能会崩溃.

标签:java,hashmap,concurrency,string-interning
来源: https://codeday.me/bug/20190612/1223909.html