本文主要说明System.out.println为啥会影响内存的可见性?
先看示例代码:
public class T implements Runnable {
private boolean flag = false;
public boolean getFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while (true){
if(getFlag()){
System.out.println(flag);
System.out.println("Yeah!!!");
break;
}
}
}
}
这是一个实现了 Runnable 的自定义线程类,里面的 run 方法也很简单,它用 while 循环去检查 flag 变量是否为 true,如果为true,就打印字符,并且退出循环。
下面启动线程:
public class A {
public static void main(String[] args) throws InterruptedException {
T t = new T();
Thread thread = new Thread(t);
thread.start();
Thread.sleep(2000);
t.setFlag(true);
}
}
现在在另外一个类的 main 方法中去启动线程,然后等待2秒后将 flag 变量修改为 true。
这段代码一看就是有问题的,线程类T的run方法会一直循环下去,它感知不到 main 方法里面已经将 flag设置成了 true。
原因是这里存在两个线程,一个是线程T,一个是main所在的主线程。这两个线程的数据是不会相互干扰的。
也就是说这两个线程里面都存在一个flag的副本。真正的 flag 这个变量存放在主内存中,两个线程的工作内存中的 flag 变量只是副本。
这样理解没问题。因为我就是要验证这个现象。
但是接下来就会出现一个奇怪的现象!!!!
我先稍微的改动一下线程类T里面的run方法,我在里面加一句打印语句,改完之后如下所示:
@Override
public void run() {
while (true){
// 新增的打印
System.out.println(111);
if(getFlag()){
System.out.println(flag);
System.out.println("Yeah!!!");
break;
}
}
}
然后我现在再去运行main方法,却惊奇的发现if里面的语句打印出来了,线程T感知到了主线程对flag的改动。这是为什么?
线程T感知到了主线程对flag的改动,说明这条打印语句已经影响到了内存可见性。可是他是如何影响的呢?
答案就在源码里面:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
原来println有一个上锁的操作。
使用了 synchronized 上锁会做以下操作:
- 获得同步锁;
- 清空工作内存;
- 从主内存拷贝对象副本到工作内存;
- 执行代码(计算或者输出等);
- 刷新主内存数据;
- 释放同步锁。
总结
这下明白了,为什么 System.out.println()方法会影响内存可见性了。
说得更细一点就是 System.out.println()方法中的 synchronized 影响了内存的可见性。
技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。