问题描述
这几天在深入了解多线程,当学习到关于线程可见性的研究时,我写 demo 突然发现一个问题:
下面的代码是模拟线程可见性的。主线程(main)先启动,然后启动子线程(ThreadVolatileDemo ),flag 初始值为 true,然后主线程将 flag 设置为 false。
由于线程的可见性,主线程的 flag = false 会存入主内存,然后子线程去读取主内存,while 循环结束。
可是!!!!情况有点不对!!!
public class ThreadVolatile {
public static void main(String[] args) {
ThreadVolatileDemo demo = new ThreadVolatileDemo();
demo.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.setFlag(false);
System.out.println("flag == false");
}
}
class ThreadVolatileDemo extends Thread {
public boolean flag = true;
//public volatile boolean flag = true;
@Override
public void run() {
System.out.println("子线程开始执行");
while (flag) {
//System.out.println("while循环中");
}
System.out.println("子线程执行结束");
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
执行结果会卡住,“子线程执行结束”这句话不会被打印出来!
我们下面修改一点代码,把主线程的休眠注释掉!
public class ThreadVolatile {
public static void main(String[] args) {
ThreadVolatileDemo demo = new ThreadVolatileDemo();
demo.start();
//try {
// Thread.sleep(3000);
//} catch (InterruptedException e) {
// e.printStackTrace();
//}
demo.setFlag(false);
System.out.println("flag == false");
}
}
class ThreadVolatileDemo extends Thread {
public boolean flag = true;
//public volatile boolean flag = true;
@Override
public void run() {
System.out.println("子线程开始执行");
while (flag) {
//System.out.println("while循环中");
}
System.out.println("子线程执行结束");
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
此时的执行结果是什么?看下图:
子线程执行结束这句话竟然打印出来了?
这代表子线程读取到了主内存中的 flag == false
问题很严重!!!
是因为线程休眠后子线程就不会去读主内存?
还是因为线程休眠后主线程不会写入主内存?
解题
其实 Thread.sleep() 不会影响线程可见性啦!
出现上面问题的原因是,Thread.sleep() 影响了线程的调度。
第一种情况
第一种情况设置了线程休眠,此时主线程休眠,此时主内存和子线程栈中的 flag = true,而子线程启动后,直接进入了 while 循环,当子线程在 while 循环的时候,主线程执行了 flag = false,因为没有使用 volatile 关键字修饰变量 flag,所以 flag 的变化不是线程可见的,因此子线程的循环仍然使用自己栈中的 flag = true。
这样的话,输出结果卡住的原因就找到了!
第二种情况
第二种情况,由于 CPU 的执行线程的特性,没有线程休眠时,很可能在 while 循环前主线程就把 flag = false 写到了主内存中,当子线程执行 while 循环的时候,子线程的栈中 flag 的值已经被刷新成了 false,因此不会卡住。
思考
我代码中还注释了一行,就是在 while 循环中打印东西,如果这一句不注释,会是什么结果呢?
结果我就不放出来了,各位可以试一试,直接上答案:
除了 volatile 关键字会刷新副本(刷新子线程中值与主内存一样),其他同步方法执行的时候也会刷新副本,比如 println。
当你在 while 循环中打印的时候,副本已经被刷新了,此时,就算加上线程休眠,结果也不会被卡住。
学无止境,上述为个人前线理解,如有不对,请各位批评指正!
END