问题
Here is the original code
//@author Brian Goetz and Tim Peierls
@ThreadSafe
public class SafePoint {
@GuardedBy("this") private int x, y;
private SafePoint(int[] a) {
this(a[0], a[1]);
}
public SafePoint(SafePoint p) {
this(p.get());
}
public SafePoint(int x, int y) {
this.set(x, y);
}
public synchronized int[] get() {
return new int[]{x, y};
}
public synchronized void set(int x, int y) {
this.x = x;
this.y = y;
}
}
Here it is fine that the private int x,y are not final because the set method in the constructor makes for a happens before relationship when calling get because they use the same lock.
Now here is the modified version and a main method that I expected to throw an AssertionError after running it for a little bit because I removed the synchronized keyword in the set method. I made it private for the constructor to be the only one calling it in case someone was going to point out that it's not thread-safe because of it, which isn't the focus of my question.
Anyhow, I've waited quite a bit now, and no AssertionErrors were thrown. Now I am weary that this modified class is somehow thread-safe, even though from what I've learned, this is not because the x and y are not final. Can someone tell me why AssertionError is still never thrown?
public class SafePointProblem {
static SafePoint sp = new SafePoint(1, 1);
public static void main(String[] args) {
new Thread(() -> {
while (true) {
final int finalI = new Random().nextInt(50);
new Thread(() -> {
sp = new SafePoint(finalI, finalI);
}).start();
}
}).start();
while (true) {
new Thread(() -> {
sp.assertSanity();
int[] xy = sp.get();
if (xy[0] != xy[1]) {
throw new AssertionError("This statement is false 1.");
}
}).start();
}
}
}
class SafePoint {
private int x, y;
public SafePoint(int x, int y) {
this.set(x, y);
}
public synchronized int[] get() {
return new int[]{x, y};
}
// I removed the synchronized from here
private void set(int x, int y) {
this.x = x;
this.y = y;
}
public void assertSanity() {
if (x != y) {
throw new AssertionError("This statement is false2.");
}
}
}
回答1:
The fact that you have run this for a lot of time does not mean anything, it just means that at the moment you did not reproduce this; may be with a different jre or CPU this could break. Especially bad since the Murphy law will guarantee that this will happen somewhere in production and you will have a nightmare to debug.
A small example is not proof of good/correct code, especially true for concurrent code - which is extremely hard (I don't even dare to say to that I fully understand it). And you do understand that this is potentially bad since there is no happens-before.
Also making those variables final will mean that you can not set them via the setters, but only in constructor. So this means you can't have a setter, thus no one can alter the fields x and y once they are set, thus get is not supposed to be synchronized at all (I am talking about your SafePoint here)
回答2:
I'm not sure this question can be answered just with JMM so you may well get some sort of undefined behavior.
To investigate the question a little bit deeper we can try to decompile it. I ran this code compiled with HotSpot C2-compiler. Here is the fragment I could found (the whole compiled code is too long):
0x00007f6b38516fbd: lock addl $0x0,(%rsp) ;*synchronization entry
; - java.util.Random::<init>@-1 (line 105)
; - com.test.SafePointProblem$lambda::run@4 (line 19)
0x00007f6b38516fc2: mov 0x10(%r10),%rax ;*invokevirtual compareAndSwapLong
; - java.util.concurrent.atomic.AtomicLong::compareAndSet@9 (line 147)
; - java.util.Random::next@32 (line 204)
; - java.util.Random::nextInt@17 (line 390)
; - com.test.SafePointProblem$lambda
I'm not a HotSpot JIT-compiler expert, but from what I can see the compiled code contains synchronizations in all runnables of yours. Some of them came from Random::next (it uses CAS) which is atomic and reset CPU store buffers.
The exhausted answer to the question "Why?" can be quite complicated and definitely platform-dependent.
来源:https://stackoverflow.com/questions/48247090/why-is-this-never-throwing-an-assertionerror-even-after-running-it-for-so-long