本文主要說明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 影響了內存的可見性。
技 術 無 他, 唯 有 熟 爾。
知 其 然, 也 知 其 所 以 然。
踏 實 一 些, 不 要 着 急, 你 想 要 的 歲 月 都 會 給 你。