多线程while循环加入输出语句导致循环结束

 javase
 

1、先看奇怪现象

在网上看到一段代码,大概现象如下所述。

当子线程while循环中加入System.out.println(i);语句以后,程序会自动停止。当去掉System.out.println(i);语句后,子线程subThread并不会停止,而是一直运行下去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StopThread {
private static boolean isEnd;
public static void main(String[] args) throws InterruptedException {
Thread subThread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (!isEnd) {
i++;
//System.out.println(i);
}
}
});
subThread.start();
TimeUnit.SECONDS.sleep(1);
isEnd = true;
}
}

2、部分错误解释

一开始看着确实奇怪,为什么输入语句能导致线程停止运行呢,于是在网上寻找了一番资料,通过对比,发现有些错误的解释,大概意思是说:println 的源码里面有 synchronized 关键字,所以会同步变量 isEnd 的值。其实这种解释是不正确的,同步关键字同步的是同步块里面的变量,但是isEnd变量并不在同步代码块中。

3、出现此现象的根本原因

其实出现这种现象的根本原因并不是由于同步代码块导致的,而是JVM会尽力保证内存的可见性,即使这个变量没有加同步关键字。也就是说,只要CPU有时间,JVM就会去尽力保证内存可见性,这种与 volatile 关键字的不同在于,volatile 关键字会强制的保证线程的可见性。而不加这个关键字,JVM 也会尽力去保证可见性,如果CPU一直处于忙碌状态,那么JVM也就没有办法了。

在上面的代码中,没有加System.out.println(i);的时候,由于循环累计操作一直占用CPU,导致JVM也无暇去顾及保证内存可见性的操作,因此主线程中把isEnd赋值为true以后,子线程subThread并没有及时拿到最新的值,所以线程会一直运行。

加了System.out.println(i);以后在println方法中,存在synchronized,其内部需要操作到IO相关的操作,是非常耗费时间的,这个时候CPU就有空闲的时间去保证内存的可见性,于是subThread中的循环就会结束。

4、其他方法实现同样的效果

其实不仅仅是输出语句能导致子线程循环的停止,也可以在 while 循环里面加上 sleep ,让 run 方法放弃 cpu ,但是不放弃锁,这个时候由于 CPU 有空闲的时候就去按照 JVM 的要求去保证内存的可见性。