假设我们有一个类Container

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
      }
}


是否可以以这样的方式对操作进行重新排序,以使从createValue引用LongHolder的方法转义该方法,从而在将其他线程放入映射之前变为可用的其他线程?在这种情况下,我们可以获取LongHolder引用形式createValue并将其传递到在地图中看不到的其他线程中的doSmth。可能吗?如果没有,请解释。

更新:
ConcurrentHashMap的Javadoc指出,检索反映了保留在其上的最新完成的更新操作的结果
  发作。 (更正式地说,给定密钥的更新操作带有一个
  与以下任何(非空)检索相关的事件发生之前
  该键报告更新后的值。)
 但是,最近完成的更新操作包括
   问题恰恰是关于重新排序时的这种微妙情况
   可能以实际更新操作的方式发生
   尚不完整,引用在put之前转义。
   ConcurrentHashMap仅说明给定密钥带有一个事实
   与该键的任何(非空)检索之前发生的关系
   换句话说,只有当我们检索到该键时,我们才能辩论
   有关此键的更新操作之前发生的问题。

我只能提出其他一些无法实现的原因:

1)重新排序规则比我想象的要严格,我们不能假定仲裁重新排序(或者可以吗?规则是什么?)。

2)put具有一些不允许重新编排的魔术属性,并且return仅在完成put之后才会发生。在那种情况下,这些属性是什么?

最佳答案

假设线程AB引用了Container实例。

(由于Container.map被声明为final,其值将被安全地发布。此外,由于它引用了ConcurrentHashMap,因此不需要进行同步就可以保持线程安全。)

现在假设线程A调用Longholder holder = container.createValue(x),并以某种方式将holder传递给线程B。您的问题是,如果B调用doSmth传递了该所有者,那么map.get(holder)调用会在地图上看到它吗?

答案是肯定的。

ConcurrentHashMap的规范说:


  “检索反映了自启动以来最新完成的更新操作的结果。(更正式的说,给定键的更新操作与该键的任何(非空)检索报告更新后的值具有事前发生的关系。 )”。


这意味着在put调用之前发生了线程A在线程get中随后发生的B调用之前发生的情况。这又意味着:


get将找到LongHolder密钥,然后
get将返回正确的值。


LongHolder对象的i字段的值也将与预期的一样,即使LongHolder是可变持有者也是如此。

(请注意,在这里声明不可变的LongHolder没有什么意义。尽管具有更简单的API,它等效于java.lang.Long...。)




  是否可以以这样的方式对操作进行重新排序,以使从LongHolder引用createValue的方法转义该方法,从而在将其放入映射之前可用于其他线程?


基本上没有相关操作如下:

      LongHolder v = new LongHolder(i);
      map.put(v, 1)
      return v;    // corrected typo



没有任何意义的源代码重新排序。
如果您要谈论的是编译器针对运行createValue的线程进行的重新排序,那么JLS不允许这样做。禁止更改线程内可见性的任何重新排序。
由于vget的属性,通过地图发布put是安全的发布。 (在实现中,存在内存障碍,禁止在getput调用周围进行有害的重新排序。)
3.是关系发生之前发生的结果。


现在,如果代码要更改为此:

      LongHolder v = new LongHolder(i);
      map.put(v, 1)
      // Somehow modify v.i
      return v;


那将是不安全出版的一种形式。由于v.iA的更改发生在put之后,因此putget中的B之间的关系发生在发生之前,不足以保证获得新的v.i值。在线程B中可见。那是因为链接不再起作用。

现在,我想如果线程createValueA调用的结果以不安全的方式传递给另一个线程(BC),则不能保证后者看到正确的值。 v.i ...如果LongHolder是可变的。但这不是createValue / doSmth代码的问题。并且通过地图发布v的值是安全的。

但是我认为关于重新排序的讨论没有抓住重点。禁止任何违反内存模型所保证的可见性语义的重新排序。 JIT编译器不允许这样做。因此,您只需要在分析之前进行操作即可。

10-07 23:01