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
    值是安全的

    但我认为,关于重新排序的讨论没有抓住重点。任何违反