如何在实践中理解本书 java 并发的示例?
How to understand an example of book java concurrency in practice?
Listing 3.15. Class at Risk of Failure if Not Properly Published.
public class Holder {
private int n;
public Holder(int n) { this.n = n; }
public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
}
我的第一个问题是javac为什么不优化if (n != n)
?
下面是我的例子demo
public class TestSync {
private int n;
public TestSync(int n) {
this.n = n;
}
public void assertSanity() {
if(n!=n)
throw new AssertionError("This statement is false");
}
private static TestSync test;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
if(test == null) test = new TestSync(2);
else test = null;
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
if(test != null)
try {
test.assertSanity();
} catch (NullPointerException e) {
}
}
}
}).start();
}
}
我的第二个问题是我做对了吗?因为我运行 demo的时候没有出现异常。
更新
1.Addition 我的第一个问题:
javap -c TestSync.class
public void assertSanity();
Code:
0: aload_0
1: getfield #3 // Field n:I
4: aload_0
5: getfield #3 // Field n:I
8: if_icmpeq 21
11: new #4 // class java/lang/AssertionError
14: dup
15: ldc #5 // String This statement is false
17: invokespecial #6 // Method java/lang/AssertionError."<init>":(Ljava/lang/Object;)V
20: athrow
21: return
我认为 javac 会将 if(n!=n)
优化为 if(false)
并缩小它。
2.Why 我还是在if(test != null)
?
后面加上try{}catch(NullPointerException e)
因为我认为字段 test
可能会在 if(test!=null)
之后被另一个线程设置 null
。
首先,javac
几乎从不优化代码,你在编译。只有当值是 编译时常量 时,才需要 javac
来在编译时计算表达式,它本身形成编译时常量,请参阅 JLS §15.28. Constant Expressions。
然而,操作在运行时得到优化,甚至没有线程同步措施也允许优化器使用乐观的假设,比如变量在两次读取之间不会改变。因此,n!=n
表达式以很低的可能性开始计算为 true
,因为读取之间的时间很短¹,并且在优化器启动后几乎永远不会是 true
。所以虽然表达式n!=n
不能保证总是 false
,在实践中不太可能遇到 true
。
当然,根据墨菲定律,无论如何,当您尝试引发该错误时永远不会发生这种情况,但偶尔会在客户那里发生,但永远不会重现...
¹ 请注意,即使第二个线程由于竞争条件读取初始 0
值,n!=n
也只会失败,如果没有再次读取初始 0
后续阅读。
Listing 3.15. Class at Risk of Failure if Not Properly Published.
public class Holder {
private int n;
public Holder(int n) { this.n = n; }
public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
}
我的第一个问题是javac为什么不优化if (n != n)
?
下面是我的例子demo
public class TestSync {
private int n;
public TestSync(int n) {
this.n = n;
}
public void assertSanity() {
if(n!=n)
throw new AssertionError("This statement is false");
}
private static TestSync test;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
if(test == null) test = new TestSync(2);
else test = null;
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
if(test != null)
try {
test.assertSanity();
} catch (NullPointerException e) {
}
}
}
}).start();
}
}
我的第二个问题是我做对了吗?因为我运行 demo的时候没有出现异常。
更新
1.Addition 我的第一个问题:
javap -c TestSync.class
public void assertSanity();
Code:
0: aload_0
1: getfield #3 // Field n:I
4: aload_0
5: getfield #3 // Field n:I
8: if_icmpeq 21
11: new #4 // class java/lang/AssertionError
14: dup
15: ldc #5 // String This statement is false
17: invokespecial #6 // Method java/lang/AssertionError."<init>":(Ljava/lang/Object;)V
20: athrow
21: return
我认为 javac 会将 if(n!=n)
优化为 if(false)
并缩小它。
2.Why 我还是在if(test != null)
?
try{}catch(NullPointerException e)
因为我认为字段 test
可能会在 if(test!=null)
之后被另一个线程设置 null
。
首先,javac
几乎从不优化代码,你在编译。只有当值是 编译时常量 时,才需要 javac
来在编译时计算表达式,它本身形成编译时常量,请参阅 JLS §15.28. Constant Expressions。
然而,操作在运行时得到优化,甚至没有线程同步措施也允许优化器使用乐观的假设,比如变量在两次读取之间不会改变。因此,n!=n
表达式以很低的可能性开始计算为 true
,因为读取之间的时间很短¹,并且在优化器启动后几乎永远不会是 true
。所以虽然表达式n!=n
不能保证总是 false
,在实践中不太可能遇到 true
。
当然,根据墨菲定律,无论如何,当您尝试引发该错误时永远不会发生这种情况,但偶尔会在客户那里发生,但永远不会重现...
¹ 请注意,即使第二个线程由于竞争条件读取初始 0
值,n!=n
也只会失败,如果没有再次读取初始 0
后续阅读。