Java 安全发布和ConcurrentHashMap
假设我们有一个类容器Java 安全发布和ConcurrentHashMap,java,concurrency,java.util.concurrent,Java,Concurrency,Java.util.concurrent,假设我们有一个类容器 class Container { private final Map<LongHolder, Integer> map = new ConcurrentHashMap<>(); static class LongHolder { private final Long i; private LongHolder(Long i) { this.i = i;
class Container {
private final Map<LongHolder, Integer> map = new ConcurrentHashMap<>();
static class LongHolder {
private final Long i;
private LongHolder(Long i) {
this.i = i;
}
}
public LongHolder createValue(Long i) {
LongHolder v = new LongHolder(i);
map.put(v, 1)
return LongHolder;
}
public void doSmth(LongHolder longHolder) {
map.get(longHolder);
... // do smth
}
}
类容器{
私有最终映射=新的ConcurrentHashMap();
静态类持龙器{
私人期末长本人;
私人长持有人(长一){
这个。i=i;
}
}
公共长持有人创造价值(长i){
长柄v=新长柄(i);
地图放置(v,1)
回程长柄;
}
公共无效doSmth(长柄长柄){
获取地图(长持有人);
…做smth
}
}
操作是否可以以这样的方式重新排序,即createValue中对LongHolder的引用会逃逸该方法,从而在将其放入映射之前对其他线程可用?在这种情况下,我们可以获取一个LongHolder reference表单createValue,并将其传递给另一个线程中的doSmth,该线程在映射中看不到它。可能吗?如果没有,请解释
更新:
ConcurrentHashMap的Javadoc状态检索反映了最近完成的更新操作的结果
开端(更正式地说,给定密钥的更新操作具有
在与的任何(非null)检索关联之前发生
该键报告更新的值。)
但是最近完成的更新操作
问题正是关于重新排序时的这一微妙情况
可能以实际更新操作的方式发生
未完成,引用在put
之前被转义
ConcurrentHashMap
仅说明给定键具有
在与该键的任何(非null)检索相关之前发生
换言之,只有当我们得到检索的关键,我们可以辩论
关于在与此键的更新操作的关系之前发生
我只能提出一些其他原因来解释为什么它不会发生:
1) 重新排序规则比我想象的更严格,我们不能假设仲裁重新排序(或者我们可以?规则是什么?)
2)
put
具有一些不允许重新排序的神奇属性,并且return
仅在完成put
后发生。在这种情况下,这些属性是什么?假设线程A
和B
引用了容器
实例
(由于Container.map
被声明为final
,它的值将被安全地发布。此外,由于它引用了ConcurrentHashMap
,因此不需要同步来维护线程安全。)
现在假设线程A
调用Longholder holder=container.createValue(x)
并以某种方式将holder
传递给线程B
。你的问题是,如果B
呼叫doSmth
经过该持有者,那么map.get(holder)
呼叫是否会在地图中看到它
答案是肯定的
ConcurrentHashMap
的规范说明如下:
“检索反映了最近完成的更新操作在开始时保持的结果。(更正式地说,给定键的更新操作与报告更新值的该键的任何(非空)检索具有“发生在之前”的关系。)”
这意味着在调用线程a
make和随后的get
调用线程B
之前有一个事件发生。这反过来意味着:
将找到get
键,然后LongHolder
将返回正确的值get
LongHolder
对象的i
字段的值也将与预期值相同,即使LongHolder
是一个可变的holder,该值也是如此
(请注意,在这里声明不可变的LongHolder
没有多大意义。它相当于java.lang.Long
…尽管使用了更简单的API。)
操作是否可以以这样的方式重新排序,即
createValue
中对LongHolder
的引用将转义该方法,从而在将其放入映射之前对其他线程可用
基本上没有。相关操作如下:
LongHolder v = new LongHolder(i);
map.put(v, 1)
return v; // corrected typo
createValue
的线程重新排序,JLS不允许这样做。禁止任何改变线程内可见性的重新排序李>
get
和put
的属性,通过地图发布v
是安全的。(在实现时,存在内存障碍,禁止围绕get
和put
调用进行有害的重新排序。) LongHolder v = new LongHolder(i);
map.put(v, 1)
// Somehow modify v.i
return v;
那将是一种不安全的出版形式。由于A
对v.i
的更改发生在put
之后,因此B
中put
和get
之间的before关系不足以保证v.i
的新值在线程B
中可见。那是因为链子不再起作用了
现在,我假设如果createValue
调用线程A
的结果以不安全的方式传递给另一个线程(B
或C
),那么后者就不能保证看到v.I
的正确值。。。如果longhholder
是可变的。但这不是createValue
/doSmth
代码的问题。通过地图发布v
值是安全的
但我认为,关于重新排序的讨论没有抓住重点。任何违反