在Java Concurrency in Practice其中一个例子中,我认为人们(至少是我)的惊喜是这样的:
public class Foo {
private int n;
public Foo(int n) {
this.n = n;
}
public void check() {
if (n != n) throw new AssertionError("huh?");
}
}
(至少对我来说)的惊喜是声称这不是线程安全的,并且不仅它不安全,而且检查方法也有可能抛出断言错误。
解释是,如果没有同步/标记n为volatile,则不同线程之间没有可见性保证,并且n的值可以在线程读取时更改。
但我想知道它在实践中发生的可能性有多大。或者更好,如果我能以某种方式复制它。
所以我试图编写会触发断言错误的代码,但没有运气。
是否有一种直接的方式来编写一个测试,证明这个可见性问题不仅仅是理论上的?
或者是在最近的JVM中发生了哪些变化?
答案 0 :(得分:6)
但我想知道在实践中发生的可能性有多大。
非常不可能的esp,因为JIT可以将n
转换为局部变量,只读一个。
极不可能的线程安全漏洞的问题是,有一天,您可能会更改一些无关紧要的内容,例如您选择的处理器或JVM,以及您的代码突然随机中断。
或者更好,如果我能以某种方式复制它。
无法保证您可以重现它。
是否有一种直接的方式来编写一个测试,证明这个可见性问题不仅仅是理论上的?
在某些情况下,是的。但是这一个很难证明,部分原因是JVM没有被阻止比JLS说的最小线程更安全。
例如,HotSpot JVM通常会按照您的预期执行,而不仅仅是文档中的最小值。例如根据javadoc,System.gc()只是一个提示,但默认情况下,HotSpot JVM每次都会这样做。
答案 1 :(得分:3)
这种情况非常不可能,但仍有可能。在加载比较中的第一个n
之前,线程必须暂停,然后在第二个比较之前和它们进行比较 - 这需要你需要的一小部分时间。超级幸运地打了它。但是如果你把这个代码写在一个超级关键的应用程序中,数百万用户每天都会在全世界范围内使用它,它迟早会发生 - 这只是一个时间问题。
无法保证您可以重现它 - 甚至可能在您的机器上无法重现。这取决于您的平台,VM,Java编译器等......
您可以将第一个n
转换为局部变量,然后暂停线程(Sleep),并在进行比较之前让第二个线程更改n。但我认为这种情况会破坏证明你案件的目的。
答案 2 :(得分:0)
如果Foo
发布不安全,理论上另一个线程在读取n
两次时会观察到两个不同的值。
由于这个原因,以下程序可能会失败。
public static Foo shared;
public static void main(String[] args)
{
new Thread(){
@Override
public void run()
{
while(true)
{
Foo foo = shared;
if(foo!=null)
foo.check();
}
}
}.start();
new Thread(){
@Override
public void run()
{
while(true)
{
shared = new Foo(1); // unsafe publication
}
}
}.start();
}
然而,几乎不可能观察到它失败了; VM可能会将n!=n
优化为false
而不会实际读取n
两次。
但我们可以展示一个等效的程序,即就Java内存模型而言,对前一个程序的有效转换,并观察它是否立即失败
static public class Foo
{
int n;
public Foo()
{
}
public void check()
{
int n1 = n;
no_op();
int n2 = n;
if (n1 != n2)
throw new AssertionError("huh?");
}
}
// calling this method has no effect on memory semantics
static void no_op()
{
if(Math.sin(1)>1) System.out.println("never");
}
public static Foo shared;
public static void main(String[] args)
{
new Thread(){
@Override
public void run()
{
while(true)
{
Foo foo = shared;
if(foo!=null)
foo.check();
}
}
}.start();
new Thread(){
@Override
public void run()
{
while(true)
{
// a valid transformation of `shared=new Foo(1)`
Foo foo = new Foo();
shared = foo;
no_op();
foo.n = 1;
}
}
}.start();
}