为什么即使在 运行 之后这么久也不会抛出 AssertionError?
Why is this never throwing an AssertionError even after running it for so long?
这里是原代码
//@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;
}
}
在这里,private int x,y 不是 final 是可以的,因为构造函数中的 set 方法在调用 get 时会产生 happens before 关系,因为它们使用相同的锁。
现在这是修改后的版本和一个主要方法,我希望它在 运行 之后抛出一个 AssertionError 一点点,因为我删除了 set 方法中的 synchronized 关键字。我将构造函数设为私有,使其成为唯一调用它的构造函数,以防有人指出它因此不是线程安全的,这不是我的问题的重点。
总之,我已经等了很久了,并没有抛出 AssertionErrors。现在我厌倦了这个修改后的 class 在某种程度上是线程安全的,即使从我所学到的,这并不是因为 x 和 y 不是最终的。有人能告诉我为什么仍然没有抛出 AssertionError 吗?
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.");
}
}
}
我不确定仅用 JMM 就可以回答这个问题,因此您很可能会遇到某种未定义的行为。
为了更深入地研究这个问题,我们可以尝试反编译它。我 运行 这段代码是用 HotSpot C2 编译器编译的。这是我能找到的片段(整个编译代码太长):
0x00007f6b38516fbd: lock addl [=10=]x0,(%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
我不是 HotSpot JIT 编译器专家,但据我所知,编译后的代码在您的所有可运行程序中都包含同步。其中一些来自 Random::next
(它使用 CAS),它是原子的并重置 CPU 存储缓冲区。
问题 "Why?" 的详尽答案可能非常复杂,而且绝对取决于平台。
你 运行 这个 很长一段时间 并不意味着什么,它只是意味着 目前 你没有复制这个;可能使用不同的 jre
或 CPU
这可能会中断。尤其糟糕,因为墨菲定律将保证这会在生产中的某个地方发生,你将有一个调试的噩梦。
一个小例子不能证明 good/correct 代码,尤其是对于并发代码——这非常困难(我什至不敢说我完全理解)。你确实明白这可能很糟糕,因为没有发生过。
同时使这些变量成为 final
将意味着您不能通过 setters
设置它们,而只能在构造函数中设置它们。所以这意味着你 不能 有一个 setter,因此没有人可以更改字段 x
和 y
一旦它们被设置,因此 get
根本不应该同步(我在这里说的是你的 SafePoint
)
这里是原代码
//@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;
}
}
在这里,private int x,y 不是 final 是可以的,因为构造函数中的 set 方法在调用 get 时会产生 happens before 关系,因为它们使用相同的锁。
现在这是修改后的版本和一个主要方法,我希望它在 运行 之后抛出一个 AssertionError 一点点,因为我删除了 set 方法中的 synchronized 关键字。我将构造函数设为私有,使其成为唯一调用它的构造函数,以防有人指出它因此不是线程安全的,这不是我的问题的重点。
总之,我已经等了很久了,并没有抛出 AssertionErrors。现在我厌倦了这个修改后的 class 在某种程度上是线程安全的,即使从我所学到的,这并不是因为 x 和 y 不是最终的。有人能告诉我为什么仍然没有抛出 AssertionError 吗?
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.");
}
}
}
我不确定仅用 JMM 就可以回答这个问题,因此您很可能会遇到某种未定义的行为。
为了更深入地研究这个问题,我们可以尝试反编译它。我 运行 这段代码是用 HotSpot C2 编译器编译的。这是我能找到的片段(整个编译代码太长):
0x00007f6b38516fbd: lock addl [=10=]x0,(%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
我不是 HotSpot JIT 编译器专家,但据我所知,编译后的代码在您的所有可运行程序中都包含同步。其中一些来自 Random::next
(它使用 CAS),它是原子的并重置 CPU 存储缓冲区。
问题 "Why?" 的详尽答案可能非常复杂,而且绝对取决于平台。
你 运行 这个 很长一段时间 并不意味着什么,它只是意味着 目前 你没有复制这个;可能使用不同的 jre
或 CPU
这可能会中断。尤其糟糕,因为墨菲定律将保证这会在生产中的某个地方发生,你将有一个调试的噩梦。
一个小例子不能证明 good/correct 代码,尤其是对于并发代码——这非常困难(我什至不敢说我完全理解)。你确实明白这可能很糟糕,因为没有发生过。
同时使这些变量成为 final
将意味着您不能通过 setters
设置它们,而只能在构造函数中设置它们。所以这意味着你 不能 有一个 setter,因此没有人可以更改字段 x
和 y
一旦它们被设置,因此 get
根本不应该同步(我在这里说的是你的 SafePoint
)