Java 意外的线程行为。可见度
我有以下代码:Java 意外的线程行为。可见度,java,multithreading,Java,Multithreading,我有以下代码: 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");
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();
}
在课堂上,我们了解了使用不同步代码时可能出现的“可见性”问题。
我理解,为了节省时间,编译器将决定将抓取turn
转到CPU中的缓存中进行循环,这意味着线程不会知道是否在RAM中更改了turn
值,因为他没有检查它
据我所知,我希望代码能像这样运行:
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.");
}
}
它们之间有什么不同
当我运行代码时,有时在两个线程都卡住之前,我会得到一些“ababa…”
我怀疑,当代码被JIT编译时,行为正在发生变化。在JIT编译之前,写操作是可见的,因为解释器正在进行写操作。JIT编译后,缓存刷新或读取已优化掉。。。因为内存模型允许这样做
我错过了什么
您缺少的是,您期望未指定的行为是一致的。不一定是这样。毕竟,这是不明确的!(这是事实,即使我提出的上述解释不正确。)
当我运行代码时,有时在两个线程都卡住之前,我会得到一些“ababa…”
我怀疑,当代码被JIT编译时,行为正在发生变化。在JIT编译之前,写操作是可见的,因为解释器正在进行写操作。JIT编译后,缓存刷新或读取已优化掉。。。因为内存模型允许这样做
我错过了什么
您缺少的是,您期望未指定的行为是一致的。不一定是这样。毕竟,这是不明确的!(这是真的,即使我上面提出的解释不正确。)事实上
turn
不是易变的,并不意味着你的代码会崩溃,只是它可能会崩溃。据我们所知,线程在任何给定时刻都可以看到假或真。缓存可以无任何原因地随机刷新,特别是线程可以保留其缓存,等等
这可能是因为您的代码正受到System.out.print
的副作用,它会在内部写入同步方法:
521 private void write(String s) {
522 try {
523 synchronized (this) {
()
synchronized的内存效应可能会刷新缓存,从而影响代码
正如@Stephen C所说,它也可能是JIT,它可能会取消布尔检查,因为它假设值不会因为另一个线程而改变
因此,在目前提到的三种不同的可能性中,它们都可能是影响代码行为的因素。可见性是一个因素,而不是一个决定因素。事实上
turn
不是易变的,并不意味着你的代码会崩溃,只是它可能会崩溃。据我们所知,线程在任何给定时刻都可以看到假或真。缓存可以无任何原因地随机刷新,特别是线程可以保留其缓存,等等
这可能是因为您的代码正受到System.out.print
的副作用,它会在内部写入同步方法:
521 private void write(String s) {
522 try {
523 synchronized (this) {
()
synchronized的内存效应可能会刷新缓存,从而影响代码
正如@Stephen C所说,它也可能是JIT,它可能会取消布尔检查,因为它假设值不会因为另一个线程而改变
因此,在目前提到的三种不同的可能性中,它们都可能是影响代码行为的因素。可见性是一个因素,而不是决定因素。我认为问题在于您添加了一个假设,即一个线程将看到另一个线程的更新。e、 g.一个布尔值可以内联到代码中,从而产生一个永远不会改变的值。@BoristSpider OP用词不当(或者OP的教授做过)-CPU缓存与此无关。CPU缓存永远不会为您提供已更改的内存位置的旧值。我们在这里讨论的是
turn
的值是否通过将其存储在CPU寄存器中并处理而不是触摸内存来“缓存”。当然,这是编译器的决定(除非你声明变量volatile,从而剥夺了编译器的选择权)。我认为问题在于你增加了一个假设,即一个线程会看到另一个线程的更新。e、 g.一个布尔值可以内联到代码中,从而产生一个永远不会改变的值。@BoristSpider OP用词不当(或者OP的教授做过)-CPU缓存与此无关。CPU缓存永远不会为您提供已更改的内存位置的旧值。我们在这里讨论的是turn
的值是否通过将其存储在CPU寄存器中并处理而不是触摸内存来“缓存”。当然,这是编译器的决定(除非您声明变量volatile,从而放弃编译器的选择)。关于打印到system.out的“意外”同步效果,您是正确的。我错过了。但这并不能解释“线程被卡住”的效果。@Stephen C while循环之间的交错可能会导致另一个线程在循环完成之前对变量进行可见的更改,因此当前线程将看到upd