Java 为什么即使运行了这么长时间,它也不会抛出断言错误?
这是原始代码Java 为什么即使运行了这么长时间,它也不会抛出断言错误?,java,multithreading,concurrency,visibility,jit,Java,Multithreading,Concurrency,Visibility,Jit,这是原始代码 //@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());
//@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;
}
}
在这里,私有int x,y不是final是很好的,因为构造函数中的set方法在调用get时会产生一个before-before关系,因为它们使用相同的锁
现在这里是修改后的版本和一个主方法,我希望在运行它一段时间后抛出一个AssertionError,因为我删除了set方法中的synchronized关键字。我将它设置为私有,以便构造函数成为唯一调用它的人,以防有人指出它因为它而不是线程安全的,这不是我问题的重点
无论如何,我已经等了很久了,没有抛出断言错误。现在我厌倦了这个修改过的类在某种程度上是线程安全的,尽管从我所学到的,这并不是因为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.");
}
}
}
我不确定这个问题是否可以用简单的语言来回答,所以你很可能会得到某种未定义的行为 为了更深入地研究这个问题,我们可以尝试对其进行反编译。我用HotSpot C2编译器编译了这段代码。下面是我能找到的片段(整个编译代码太长):
0x00007f6b38516fbd:锁定地址$0x0,(%rsp)*同步条目
; - java.util.Random::@-1(第105行)
; - com.test.SafePointProblem$lambda::run@4(第19行)
0x00007f6b38516fc2:mov 0x10(%r10),%rax*invokevirtual compareAndSwapLong
; - java.util.concurrent.AtomicLong::compareAndSet@9(第147行)
; - java.util.Random::next@32(第204行)
; - java.util.Random::nextInt@17(第390行)
; - com.test.SafePointProblem$lambda
我不是HotSpot JIT编译器专家,但从我所看到的情况来看,编译后的代码在您的所有可运行程序中都包含同步。其中一些来自于原子和重置CPU存储缓冲区的Random::next
(它使用CAS)
“为什么?”这个问题的详尽答案可能相当复杂,而且肯定依赖于平台。您已经运行了很多时间,这一事实并不意味着什么,它只是意味着目前您没有复制它;可能使用不同的
jre
或CPU
这可能会中断。尤其糟糕,因为墨菲定律将保证这将发生在生产中的某个地方,您将有一个噩梦要调试
一个小的例子并不能证明代码是好的/正确的,特别是对于并发代码,这是非常困难的(我甚至不敢说我完全理解它)。你知道这可能是不好的,因为以前没有发生过
另外,将这些变量设置为final将意味着您不能通过setters设置它们,而只能在构造函数中设置它们。这意味着你不能有一个setter,因此没有人可以在设置后改变字段
x
和y
,因此get
根本不应该是同步的(我在这里谈论的是你的SafePoint
)实际上我试图调查是否有可能抛出AssertionError
。可能HotSpot服务器编译器进行了优化,但仍然不允许抛出错误。我所发现的只是来自随机的同步点和启动线程。。。不太有用…也是投票,是的,我知道最后一个字段及其限制。听到在不同的jre或cpu上这种情况是如何发生的,这很有趣,感谢您确认对这种情况的关注before@katiex7np,但我真的无法理解被接受的答案(我不在乎你接受哪一个)是如何解释事情的。lock addl
或synchronization entry
触发了吗?啊,我以为random.nextint就是我之前所做的telp@katiex7别误会,但是C1
编译器将生成的任何代码在这种情况下都是无关紧要的,唯一需要依赖的是规范——正如您已经得出相同的结论,您的代码在JLS规则下是不正确的,IMO是这里唯一重要的东西。这是一个非常有趣的答案。我可能需要做一个forloop,增加一个计数器,而不是使用random.nextint,然后看看会发生什么Upvoted@St.Antario我真的不知道这段代码实际上证明了什么lock addl
是一个StoreLoad
屏障。你在这里的意思是什么?@St.Antario还有一小部分代码不是规范的证明(或相反),因此即使某个编译器会做一些事情,其他编译器也可能不会。唯一需要遵守的是规范。@Eugene只是更仔细地查看了代码。必须同意,简单的负载围栏在这里不能证明一致性。此外,我看不到如何在此处获取AssertionError
。@Eugene,因为读/写引用是原子的,新的finalI=new Random()。如果没有使用prefetchnta
指令,则在发布引用时(即使是不安全的),nextInt(50)
hbnew SafePoint(finalI,finalI)
。。。我看不出有什么办法。。。
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