曾經有遇到過這樣一個問題,有一個共享變量keepRunning=true
,線程A中執行while (keepRunning);
,線程B中執行keepRunning = false;
,在main函數中同時開啓A,B線程,而後會發現程序會一直運行且不會退出。說白了這其實就是一個典型的可見性問題,A線程並不知道keepRunning
已經被修改過了,故未將修改後的keepRunning
變量的值從主內存中讀取到線程緩存中來。html
上面的問題等價於下面的代碼段:緩存
1/**
2 * @author mars_jun
3 */
4public class NoVisibility_Demonstration extends Thread {
5 boolean keepRunning = true;
6
7 public static void main(String[] args) throws InterruptedException {
8 NoVisibility_Demonstration t = new NoVisibility_Demonstration();
9 t.start();
10 System.out.println("start: " + t.keepRunning);
11 Thread.sleep(1000);
12 t.keepRunning = false;
13 System.out.println("end: " +t.keepRunning);
14 }
15
16 public void run() {
17 int x = 1;
18 while (keepRunning) {
19 //System.out.println("若是你不註釋這一行,程序會正常中止!");
20 x++;
21
22 }
23 System.out.println("x:" + x);
24 }
25}
複製代碼
按上述代碼直接運行,你會發如今打印完end: false
以後,程序並無正常的退出,而是在一直跑着while (keepRunning)
這個死循環。可是咱們嘗試着將其中註釋的代碼System.out.println("若是你不註釋這一行,程序會正常中止!");
給取消掉註釋,再運行一次上面的代碼,就會發現程序會跑一段時間後正常退出。看到這裏你們也許會感到奇怪,在進行System.out.println
這個IO操做後,線程t居然讀到了主線程寫入的t.keepRunning = false
這個值,而後致使while循環退出了。這裏就不得不去看下println
這個方法的源碼了。markdown
1 public void println(String x) {
2 synchronized (this) {
3 print(x);
4 newLine();
5 }
6 }
複製代碼
這裏咱們會發現println
方法是一個同步的方法。你們都知道用synchronized
這個關鍵字修飾的方法或者代碼塊能保證代碼串行化的執行(同一時間只能有一個線程獲取執行權限),在Doug Lea大神的Concurrent Programming in Java一書中有這樣一個片斷來描述synchronized
這個關鍵字:app
In essence, releasing a lock forces a flush of all writes from working memory employed by the thread, and acquiring a lock forces a (re)load of the values of accessible fields. While lock actions provide exclusion only for the operations performed within a synchronized method or block, these memory effects are defined to cover all fields used by the thread performing the action.ide
簡單翻譯一下:從本質上來講,當線程釋放一個鎖時會強制性的將工做內存中以前全部的寫操做都刷新到主內存中去,而獲取一個鎖則會強制性的加載可訪問到的值到線程工做內存中來。雖然鎖操做只對同步方法和同步代碼塊這一塊起到做用,可是影響的倒是線程執行操做所使用的全部字段。
這也就解釋了爲何加上System.out.println("若是你不註釋這一行,程序會正常中止!");
這句代碼後,線程t可以讀取到修改後的keepRunning
的值了。對於這個問題上,有些人的說法是:打印是IO操做,而IO操做會引發線程的切換,線程切換會致使線程本來的緩存失效,從而也會讀取到修改後的值。這裏我認爲這種說法也是有道理的,我嘗試着將打印換成File file = new File("G://1.txt");
這句代碼,程序也可以正常的結束。固然,在這裏你們也能夠嘗試將將打印替換成synchronized(NoVisibility_Demonstration.class){ }
這句空同步代碼塊,發現程序也可以正常結束。函數
針對上述問題,最起碼能夠得出一個結論:當進行IO操做或者線程內部調用synchronized
修飾的方法或者同步代碼塊時,線程的緩存會進行刷新,也就是會感知到共享變量的變化。固然這也只是針對非volatile
修飾的變量而言,當變量被申明爲volatile
的時候,每次使用該變量都會從主內存中進行讀取。(這裏對volatile
不太熟悉的能夠去看個人相關文章淺析volatile原理及其使用)oop
只有在如下條件下,才能保證一個線程對字段的更改對其餘線程可見:ui