線程的緩存何時(shí)刷新?

前言

曾經(jīng)有遇到過這樣一個(gè)問題,有一個(gè)共享變量keepRunning=true,線程A中執(zhí)行while (keepRunning);,線程B中執(zhí)行keepRunning = false;,在main函數(shù)中同時(shí)開啟A,B線程,然后會(huì)發(fā)現(xiàn)程序會(huì)一直運(yùn)行且不會(huì)退出。說白了這其實(shí)就是一個(gè)典型的可見性問題,A線程并不知道keepRunning已經(jīng)被修改過了,故未將修改后的keepRunning變量的值從主內(nèi)存中讀取到線程緩存中來。


舉例

上面的問題等價(jià)于下面的代碼段:

/**
 * @author mars_jun
 */
public class NoVisibility_Demonstration extends Thread {
    boolean keepRunning = true;

    public static void main(String[] args) throws InterruptedException {
        NoVisibility_Demonstration t = new NoVisibility_Demonstration();
        t.start();
        System.out.println("start: " + t.keepRunning);
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println("end: " +t.keepRunning);
    }

    public void run() {
        int x = 1;
        while (keepRunning) {
            //System.out.println("如果你不注釋這一行,程序會(huì)正常停止!");
            x++;

        }
        System.out.println("x:" + x);
    }
}

按上述代碼直接運(yùn)行,你會(huì)發(fā)現(xiàn)在打印完end: false之后,程序并沒有正常的退出,而是在一直跑著while (keepRunning)這個(gè)死循環(huán)。但是我們嘗試著將其中注釋的代碼System.out.println("如果你不注釋這一行,程序會(huì)正常停止!");給取消掉注釋,再運(yùn)行一次上面的代碼,就會(huì)發(fā)現(xiàn)程序會(huì)跑一段時(shí)間后正常退出。看到這里大家也許會(huì)感到奇怪,在進(jìn)行System.out.println這個(gè)IO操作后,線程t竟然讀到了主線程寫入的t.keepRunning = false這個(gè)值,然后導(dǎo)致while循環(huán)退出了。這里就不得不去看下println這個(gè)方法的源碼了。

    public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

這里我們會(huì)發(fā)現(xiàn)println方法是一個(gè)同步的方法。大家都知道用synchronized這個(gè)關(guān)鍵字修飾的方法或者代碼塊能保證代碼串行化的執(zhí)行(同一時(shí)間只能有一個(gè)線程獲取執(zhí)行權(quán)限),在Doug Lea大神的Concurrent Programming in Java一書中有這樣一個(gè)片段來描述synchronized這個(gè)關(guān)鍵字:

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.

簡單翻譯一下:從本質(zhì)上來說,當(dāng)線程釋放一個(gè)鎖時(shí)會(huì)強(qiáng)制性的將工作內(nèi)存中之前所有的寫操作都刷新到主內(nèi)存中去,而獲取一個(gè)鎖則會(huì)強(qiáng)制性的加載可訪問到的值到線程工作內(nèi)存中來。雖然鎖操作只對(duì)同步方法和同步代碼塊這一塊起到作用,但是影響的卻是線程執(zhí)行操作所使用的所有字段。
這也就解釋了為什么加上System.out.println("如果你不注釋這一行,程序會(huì)正常停止!");這句代碼后,線程t能夠讀取到修改后的keepRunning的值了。對(duì)于這個(gè)問題上,有些人的說法是:打印是IO操作,而IO操作會(huì)引起線程的切換,線程切換會(huì)導(dǎo)致線程原本的緩存失效,從而也會(huì)讀取到修改后的值。這里我認(rèn)為這種說法也是有道理的,我嘗試著將打印換成File file = new File("G://1.txt");這句代碼,程序也能夠正常的結(jié)束。當(dāng)然,在這里大家也可以嘗試將將打印替換成synchronized(NoVisibility_Demonstration.class){ }這句空同步代碼塊,發(fā)現(xiàn)程序也能夠正常結(jié)束。


結(jié)論

針對(duì)上述問題,最起碼可以得出一個(gè)結(jié)論:當(dāng)進(jìn)行IO操作或者線程內(nèi)部調(diào)用synchronized修飾的方法或者同步代碼塊時(shí),線程的緩存會(huì)進(jìn)行刷新,也就是會(huì)感知到共享變量的變化。當(dāng)然這也只是針對(duì)非volatile修飾的變量而言,當(dāng)變量被申明為volatile的時(shí)候,每次使用該變量都會(huì)從主內(nèi)存中進(jìn)行讀取。(這里對(duì)volatile不太熟悉的可以去看我的相關(guān)文章淺析volatile原理及其使用


總結(jié)

只有在以下條件下,才能保證一個(gè)線程對(duì)字段的更改對(duì)其他線程可見:

  1. 寫入線程釋放同步鎖,讀取線程隨后獲取相同的同步鎖。釋放鎖的時(shí)候會(huì)強(qiáng)制從線程使用的工作內(nèi)存中刷新所有寫入,并且在獲取鎖的時(shí)候會(huì)強(qiáng)制重新加載可訪問字段的值。
  2. 如果一個(gè)字段被聲明為volatile,則寫入線程會(huì)立即將修改后的值同步到主內(nèi)存。讀取線程必須在每次訪問時(shí)重新加載volatile字段的值。
  3. 線程第一次訪問一個(gè)對(duì)象的某個(gè)字段時(shí),它會(huì)看到字段的初始值或來自某個(gè)其他線程寫入的值。
  4. 當(dāng)一個(gè)線程終止時(shí),所有寫入的變量都被刷新到主內(nèi)存。例如:現(xiàn)有線程A,B,在B線程中調(diào)用A.join(),那么在B中可以保證看到A線程產(chǎn)生的影響。

END

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,887評(píng)論 11 349
  • 線程同步 在大多數(shù)實(shí)際的多線程應(yīng)用中, 兩個(gè)或兩個(gè)以上的線程需要共享對(duì)同一數(shù)據(jù)的存取。多個(gè)線程或者進(jìn)程在讀寫一個(gè)共...
    Steven1997閱讀 2,063評(píng)論 0 3
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,917評(píng)論 0 11
  • 本文由幣乎社區(qū)(bihu.com)內(nèi)容支持計(jì)劃獎(jiǎng)勵(lì) 1、只投資自己看得懂的。 2、不要貸款投資、不要玩杠桿!不要玩...
    f9a94f90e816閱讀 502評(píng)論 0 1
  • 主啊,憐憫我們! 有時(shí)我的生命在黑暗中睜開眼睛 感到人群盲目焦慮地 穿越大街,向奇跡涌去 而隱形的我站在原地不動(dòng) ...
    弦音尋晨醉閱讀 901評(píng)論 4 18

友情鏈接更多精彩內(nèi)容