我有以下代码:
public static boolean turn = true;
public static void main(String[] args) {
Runnable r1 = new Runnable() {
public void run() {
while (true) {
while (turn) {
System.out.print("a");
turn = false;
}
}
}
};
Runnable r2 = new Runnable() {
public void run() {
while (true) {
while (!turn) {
System.out.print("b");
turn = true;
}
}
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
在课堂上我们已经了解了" Visibility"使用未同步的代码时可能出现的问题。
我知道为了节省时间,编译器将决定抢占CPU中的缓存turn
,这意味着线程将不知道turn
值是否在RAM因为他没有检查它。
根据我的理解,我希望代码能够像这样运行:
T1会看到转为真 - >进入循环并打印 - >改变为假 - >卡住了
T2会认为没有改变 - >会卡住
我希望如果T1将在T2之前开始:只有' a'将打印并且两个线程将在无限循环中运行而不打印任何其他内容
但是,当我运行代码时,我会得到一些" ababa ...."在两个线程都卡住之前。
我错过了什么?
编辑:
以下代码完成了我的预期:线程将以无限循环运行:
public class Test extends Thread {
boolean keepRunning = true;
public void run() {
long count = 0;
while (keepRunning) {
count++;
}
System.out.println("Thread terminated." + count);
}
public static void main(String[] args) throws InterruptedException {
Test t = new Test();
t.start();
Thread.sleep(1000);
t.keepRunning = false;
System.out.println("keepRunning set to false.");
}
}
他们彼此有什么不同?
答案 0 :(得分:5)
当我运行代码时,有时我会得到一些" ababa ...."在两个线程都卡住之前。
我怀疑发生的事情是代码是JIT编译时行为正在发生变化。在JIT编译之前,写入是可见的,因为解释器正在执行直写。在JIT编译之后,缓存刷新或读取已被优化...因为内存模型允许这样做。
我错过了什么?
您缺少的是您希望未指定的行为保持一致。它不是必须的。毕竟,它是未指定的! (即使我上面提出的解释不正确,也是如此。)
答案 1 :(得分:1)
turn
不易变的事实并不意味着你的代码会破坏,只是它可能会破坏。就我们所知,线程在任何特定时刻都可能看到错误或真实。缓存可以随意刷新,特别是线程可以保留其缓存等等。
可能是因为您的代码遇到了来自System.out.print
的副作用,它在内部写入同步方法:
521 private void write(String s) {
522 try {
523 synchronized (this) {
synchronized的内存效果可能会刷新缓存,从而影响您的代码。
正如@Stephen C所说,它也可能是JIT,它可能会提升布尔检查,因为它假定该值不能因另一个线程而改变。
因此,在目前为止提到的三种不同的可能性中,它们都可能是影响代码行为的因素。可见性是一个因素,而不是决定因素。