JCIP警告我们不要发布不正确的对象(参见here)。如果对象是可变的,JVM可能会决定在初始化完成之前发布该对象。
所以代码
class Holder {public int h = 0;public Holder(int _h) {h = _h;}}
a = new Holder(10);
可以有效地
a = new Holder();
// inlined constructor
a.h = 10;
然后当不同的线程访问a.h
时,假设它永远不会为0,它可能会失败
// Thread #1 executing
a = new Holder();
// Thread #2 preempts
if (a != null) System.out.println("result: "+(100/a.h)); // oops...
我试图证明这个问题。但是我写的代码没有证明这一点。我做的是持有人
static class Holder {
int h = 0;
int dummy = 0;
public Holder(int h) {
// if construction takes time, maybe I have better chance creating the bug...
for (long i=0;i<1000*1000;i++) {
dummy += i*(-1+(i%2*2));
}
this.h = h;
}
public int verifyNonZero() {
return 1/h;
}
}
然后我运行了1000个线程,将new Holder()
发布到静态holder
变量,以及运行holder.verifyNonZero()
的其他1000个线程。请参阅full gist。
即使使用-server
,它对Java 1.6也没有帮助。
答案 0 :(得分:3)
事实证明,它不能保证安全并不意味着您的环境中出现问题。
据我所知,没有证据表明这种行为可以在现代JVM上得到证明。但是,有证据表明一些较旧的JVM受到了影响:
另请注意,乱序执行不仅可以由JVM引起,还可以由CPU引起。但是,x86 CPU的内存模型非常保守(Who ordered memory fences on an x86?),因此这种行为也不会由常用的CPU引起。
所以,我认为你不可能在典型的现代环境中证明这个问题。
答案 1 :(得分:0)
你是如何运行考试的? [编辑:哦,你链接到代码!]
JMM保证在给定的线程中,你将无法看到乱序的东西 - 也就是说,如果同一个线程正在实例化 Holder
并检查它,它将永远不会看到一个未初始化的状态。
因此,您必须在一个线程中实例化一个Holder,然后在另一个线程中查看它。你如何分享那个实例?常用方法(易失性静态,同步或线程安全集合等)在Holder由其实例化线程发布之间以及何时被其他线程读取之间建立了先发生关系。
如果您想要不安全的发布,您的主要选项是:
<击> 撞击>
即便如此,你还是依靠时间来纠正错误。正如你所做的那样,我只是使用静态来复制这些种类的运气并不是很幸运;我认为代码不够复杂,不足以让CPU进行花哨的缓存。
IBM有一个实验性的javaagent ConTest,它故意试图利用并发错误。我在一段故意线程危险的队列中给了它一个镜头(我相信这是一个故意的僵局)。没有ConTest,队列很少失败;有了它,失败率大约占30%。我不确定ConTest在内存可见性比赛中是好还是坏。