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

Flo*_*low 4 java concurrency hashmap string-interning

我最近遇到了以下构造

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

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

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

dim*_*414 7

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

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

Thread 1                Thread 2                 Thread 3
synchronized("a") {     synchronized("a") {      synchronized("b") {
  map.remove("a");        map.remove("a");         map.remove("b");
}                       }                        }
Run Code Online (Sandbox Code Playgroud)

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

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

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

另请参阅:Java synchronized block与Collections.synchronizedMap


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