java多線程并發(fā)技術(shù)之Volatile

前言:

這個(gè)volatile真的是折磨我了很久,因?yàn)閷?duì)于Android開發(fā)來說確實(shí)不是很重要,但是面試啊什么的會(huì)經(jīng)常遇到。上網(wǎng)搜一下全是什么可見性、原子性然后搞幾個(gè)無關(guān)痛癢的例子也說不到點(diǎn)上,看完以后感覺懂了,但是仔細(xì)琢磨一下還是有邏輯漏洞。經(jīng)過長(zhǎng)時(shí)間查資料和思考后決定寫下自己的理解。

一、知識(shí)點(diǎn)(可以先看樣例再看這個(gè))

  1. Volatile中文意思是“不穩(wěn)定的”,它只能用來修飾變量。那當(dāng)它來修飾變量時(shí)不就是說明這個(gè)變量是不穩(wěn)定的嗎,妙!
  1. 線程內(nèi)存管理模型(這個(gè)很簡(jiǎn)單但是很重要哦?。?br>

    圖片來自《java多線程編程核心技術(shù)》

    一句話總結(jié):每個(gè)線程所占用的內(nèi)存都是相互獨(dú)立的,并且與主內(nèi)存也是相互獨(dú)立的。(只總結(jié)了一下重點(diǎn),具體細(xì)節(jié)自己去挖掘去體驗(yàn))哦!原來是這樣,在線程想要修改主內(nèi)存中某個(gè)變量的值(比如某個(gè)全局變量)原來要經(jīng)過至少三步,第一步讀取數(shù)據(jù)到自己的工作內(nèi)存,第二步修改,第三步寫入主內(nèi)存。

  2. 可見性是當(dāng)一個(gè)線程修改了共享變量時(shí),另一個(gè)線程可以讀取到這個(gè)修改后的值(注意線程中操作共享變量的步驟),我們平時(shí)見到的大多都有可見性,那舉一個(gè)不可見性的例子,下面的例子是由于不可見性導(dǎo)致的死循環(huán)。

  3. Volatile關(guān)鍵字的作用是保證了變量在線程的可見性,它提示線程每次從共享內(nèi)存中讀取變量,而不是從私有內(nèi)存中讀取,這樣就保證了數(shù)據(jù)的可見性。

二、樣例分析

public class MyClass {
    private  static boolean mRunning = true;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while(MyClass.mRunning){
                    i++;

                   /* for(int k=0;k<100000;k++){
                        new Object();
                    }*/
                    
                }
                System.out.println("程序退出");
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                mRunning = false;  //設(shè)置mRunning為false,使上面的線程結(jié)束while循環(huán)
            }
        }).start();
    }
}

這段代碼應(yīng)該很好理解吧!第一個(gè)線程運(yùn)行以后在while中執(zhí)行i++,然后暫停一秒執(zhí)行第二個(gè)線程,第二個(gè)線程將標(biāo)志位設(shè)為false,讓第一個(gè)線程結(jié)束循環(huán)。
按照正常邏輯程序運(yùn)行后應(yīng)該會(huì)打印“程序退出”四個(gè)大字對(duì)哇!但是人世間往往事與愿違,運(yùn)行以后發(fā)現(xiàn)程序根本就不會(huì)停止。但是取消掉代碼中的注釋后發(fā)現(xiàn)程序可以正常停止了,更讓人疑惑的是注釋的代碼根本沒有任何意義?。?,那么就有了兩個(gè)疑惑(^ _ ^)

  • Q1:為什么注釋代碼后程序不會(huì)終止?
    A1:因?yàn)?boolean mRunning=true 的變量值被前面線程(簡(jiǎn)稱線程A)加載到自己的工作內(nèi)存,在后面的線程(簡(jiǎn)稱線程B)改變 boolean mRunning=false 之后不一定會(huì)立馬寫入主存(不過這道題中應(yīng)該會(huì)馬上寫入主存,因?yàn)榫€程執(zhí)行完 is=false之后線程就要退出了),即便立馬寫入了主存后線程A也不一定馬上load到工作內(nèi)存中,所以程序一直不會(huì)終止?這個(gè)是我們大多數(shù)人想到的,但其實(shí)JVM針對(duì)現(xiàn)在的硬件水平已經(jīng)做了很大程度的優(yōu)化,基本上很大程度的保障了工作內(nèi)存和主內(nèi)存的及時(shí)同步,相當(dāng)于默認(rèn)使用了volatile。但只是最大程度!在CPU資源一直被占用的時(shí)候,工作內(nèi)存與主內(nèi)存中間的同步,也就是變量的可見性就會(huì)不那么及時(shí)!
  • Q2:為什么取消注釋后程序就可以終止了?
    A2:因?yàn)槲覀冎喇?dāng)CPU在被占用的時(shí)候,數(shù)據(jù)的可見性得不到很好的保證。就像上面的例子中,沒有添加代碼之前,程序會(huì)一直循環(huán)做i++操作,所以CPU會(huì)被運(yùn)算占用;而對(duì)于大量的new Object()操作來說,CPU已經(jīng)不是主要站時(shí)間的操作,真正的耗時(shí)應(yīng)該在內(nèi)存的分配上(因?yàn)镃PU的處理速度明顯快過內(nèi)存,不然也不會(huì)有CPU的寄存器了),所以CPU空閑后會(huì)遵循jvm優(yōu)化基準(zhǔn),盡可能快的保證數(shù)據(jù)的可見性,從而從主存將mRunning變量同步到工作內(nèi)存中,最終導(dǎo)致程序的結(jié)束;
  • Q3:說了這么多貌似和Volatile沒什么關(guān)系啊
    A3:怎么可能沒有關(guān)系,如樣例中的代碼,現(xiàn)在用Volatile修飾mRunning變量,運(yùn)行代碼后發(fā)現(xiàn)代碼可以正常停止,這說明Volatile解決了一個(gè)問題,也就是不管CPU有多忙,當(dāng)線程中讀取Volatile修飾的變量時(shí)都會(huì)保證該變量的可見性,也就是會(huì)刷新工作內(nèi)存中變量的數(shù)據(jù),使得自己工作內(nèi)存中的數(shù)據(jù)與共享內(nèi)存中數(shù)據(jù)保持一致,所以就不會(huì)出現(xiàn)Q1問題中描述的現(xiàn)象,CPU太忙導(dǎo)致來不及更新工作內(nèi)存中的數(shù)據(jù)。

參考來源:
1.并發(fā)編程網(wǎng)
2.趣談Java變量的可見性問題
3.《java多線程編程核心技術(shù)》

?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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