dcnh35 dcnh35 - 1 month ago 13
Java Question

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.");
}
}


My first question is why javac not optimize
if (n != n)
?

The following is my demo for the example

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();
}
}


And my second question is Did I do the right thing? Because it occurs no exception when I run the demo.

UPDATE

1.Addition to my first question:
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


I thinked javac would optimize
if(n!=n)
to
if(false)
and shrink it.

2.Why I still add
try{}catch(NullPointerException e)
after
if(test != null)
?

Because I think field
test
may be setted
null
by the other thread after
if(test!=null)
.

Answer

First of all, javac almost never optimizes the code, you’re compiling. Only when the values are compile-time constants, javac is required to evaluate expressions at compile-time, which itself form compile-time constants, see JLS §15.28. Constant Expressions.

However, operations get optimized at runtime and it is even the absence of thread synchronization measures that allows the optimizer to use optimistic assumptions, like that a variable won’t change between two reads. So the n!=n expression starts with a low likelihood of ever evaluate to true due to the short time between the reads¹ and will almost never be true after the optimizer kicked in. So while the expression n!=n is not guaranteed to be always false, it’s unlikely to ever encounter it to be true in practice.

Of course, according to Murphy’s Law, it will never happen when you try to provoke that error anyway, but once in a while at the customer, but never reproducible…

¹ Note that even if the second thread reads the initial 0 value due to the race condition, n!=n will only fail, if does not read the initial 0 again in the subsequent read.