How deep volatile publication guarantees?

一曲冷凌霜 提交于 2019-11-30 02:25:49

In the realm of current Java Memory Model, volatile does not equal final. In other words, you cannot replace final with volatile, and think the safe construction guarantees are the same. Notably, this can theoretically happen:

public class M {
  volatile int x;
  M(int v) { this.x = v; }
  int x() { return x; }
}

// thread 1
m = new M(42);

// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // allowed to print "0"

So, writing the volatile field in constructor is not as safe.

Intuition: there is a race on m in the example above. That race is not eliminated by making the field M.x volatile, only making the m itself volatile would help. In other words, volatile modifier in that example is at the wrong place to be useful. In safe publication, you have to have "writes -> volatile write -> volatile read that observes volatile write -> reads (now observing writes prior the volatile write)", and instead you have "volatile write -> write -> read -> volatile read (that does not observe the volatile write)".

Trivia 1: This property means we can optimize volatile-s much more aggressively in constructors. This corroborates the intuition that unobserved volatile store (and indeed it is not observed until constructor with non-escaping this finishes) can be relaxed.

Trivia 2: This also means you cannot safely initialize volatile variables. Replace M with AtomicInteger in the example above, and you have a peculiar real-life behavior! Call new AtomicInteger(42) in one thread, publish the instance unsafely, and do get() in another thread -- are you guaranteed to observe 42? JMM, as stated, says "nope". Newer revisions of Java Memory Model strive to guarantee safe construction for all initializations, to capture this case. And many non-x86 ports where that matters have already strengthened this to be safe.

Trivia 3: Doug Lea: "This final vs volatile issue has led to some twisty constructions in java.util.concurrent to allow 0 as the base/default value in cases where it would not naturally be. This rule sucks and should be changed."

That said, the example can be made more cunning:

public class C {
  int v;
  C(int v) { this.x = v; }
  int x() { return x; }    
}

public class M {
  volatile C c;
  M(int v) { this.c = new C(v); }
  int x() { 
    while (c == null); // wait!
    return c.x();
  }
}

// thread 1
m = new M(42);

// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // always prints "42"

If there is a transitive read through volatile field after volatile read observed the value written by volatile write in constructor, the usual safe publication rules kick in.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!