Java HashMap<;字符串,值>;。remove()通过在键上使用String.intern()进行同步,这是否有效?或者这是坏代码?

Java HashMap<;字符串,值>;。remove()通过在键上使用String.intern()进行同步,这是否有效?或者这是坏代码?,java,concurrency,hashmap,string-interning,Java,Concurrency,Hashmap,String Interning,我最近遇到了以下构造 Map Map=newhashmap(); ... 值getValue(字符串键){ 已同步(key.intern()){ 返回地图。删除(键); } } 鉴于此,我怀疑这会比使用synchronized、Collections.synchronizedMap(Map)或ConcurrentHashMap更好。但即使这个构造比这个特殊情况下的所有其他方法都要快:这是正确同步的吗?我怀疑这是线程安全的,因为在重新组织哈希表时可能会发生删除。但即使这样做行得通,我怀疑代码可能

我最近遇到了以下构造

Map Map=newhashmap();
...
值getValue(字符串键){
已同步(key.intern()){
返回地图。删除(键);
}
}
鉴于此,我怀疑这会比使用
synchronized
Collections.synchronizedMap(Map)
ConcurrentHashMap
更好。但即使这个构造比这个特殊情况下的所有其他方法都要快:这是正确同步的吗?我怀疑这是线程安全的,因为在重新组织哈希表时可能会发生删除。但即使这样做行得通,我怀疑代码可能会被破坏,因为它指出:

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


这不足以从多个线程安全访问
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”
。现在两个不同的同步块正在同时与
map
交互,所有赌注都关闭。不久,您的
HashMap
将被损坏

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

ConcurrentHashMap
通过在映射中所有键的子集上进行内部锁定,正确地完成了我认为您发布的代码试图完成的任务。通过这种方式,多个线程可以安全地访问不同线程上的不同密钥,并且不会相互阻塞——但是如果密钥恰好位于同一个bucket中,map仍然会阻塞。可以使用构造函数参数修改此行为

另见:



另一方面,为了论证起见,我们假设
synchronized(key.intern())
是并发访问
HashMap
的合理方式。这仍然非常容易出错。如果应用程序中只有一个地方未能调用键上的
.intern()
,那么一切都可能崩溃。

它是否很好地放在静态初始值设定项或构造函数中?啊,什么都没有。算了吧。@Dan Getz-你当然是对的!我将删除我的评论。虽然托管字符串是线程安全的,但在不同的键上同步等同于在不同的托管字符串上同步,因此是完全不安全的!请注意,您发布的代码片段没有并发问题,因为多个线程没有访问
map
。假设您的实际应用程序正在同时使用
map
,但我只是想说出来。我想说的是,不能保证thread1和thread2在同一个对象上同步(我不知道JLS是否说这些字符串总是从池中使用,因此是从相同的实例中使用的)。但除此之外,是的,我也认为这样的模式被打破了。我并没有简单地为了简洁而包含
.intern()
调用,但现代版本的Java确实会将所有字符串常量都插入。然而,不管你是否确保你的密钥被托管,同步密钥都是一个坏模式。