Java基礎(chǔ)

01 Volatile

Java面試熱門內(nèi)容精講之——并發(fā)編程volatile
https://www.bilibili.com/video/BV1BJ411j7qb/?spm_id_from=333.999.0.0&vd_source=b5ef87e2f0a873011363576f8bdcba61
###1.volatile關(guān)鍵字概覽
    多線程下變量訪問的不可見性
    變量不可見性內(nèi)存語義
        JMM(Java Memory Model)java內(nèi)部模型
        所有共享變量存儲在主內(nèi)存中,變量指的是實例變量和類變量,不包含局部變量,因為局部變量是線程私有的
        每一個線程存在自己的工作內(nèi)存,線程的工作內(nèi)存保留了線程使用的共享變量副本; 
        線程對變量的所有操作(讀,?。┒急仨氃诠ぷ鲀?nèi)存中完成,而不能直接讀寫主內(nèi)存中的變量
        不同線程之間也不能直接訪問對方工作內(nèi)存中的變量,線程間變量的值的傳遞需要通過主內(nèi)存中轉(zhuǎn)來完成
    變量不可見性的解決方案:
        如何在多線程下訪問共享變量的可見性,也就是實現(xiàn)一個線程修改變量后,對其他線程可見?
        1.加鎖 synchronized關(guān)鍵字
            某個線程進(jìn)入synchronized代碼塊前后,執(zhí)行過程:線程獲得鎖;清空工作內(nèi)存;從主內(nèi)存拷貝共享變量最新的值到工作內(nèi)存成為副本;執(zhí)行代碼;將修改后的副本的值刷新回主內(nèi)存中;線程釋放鎖
        2.volatile關(guān)鍵字
            volatile保證不同線程對共享變量操作的可見性,也就是說一個線程修改了volatile修飾的變量,當(dāng)修改寫回主內(nèi)存時,另外一個線程立即看到最新的值
###2.volatile的其他特性
    volatile可以實現(xiàn)并發(fā)下共享變量的可見性
    volatile不能保證原子性操作:
        原子性是指一次操作或者多次操作,要么所有的操作全部得到執(zhí)行,要么所有的操作都不執(zhí)行
        count++操作不是原子性操作:1.從主內(nèi)存中讀取數(shù)據(jù)到工作內(nèi)存;2.對工作內(nèi)存中的數(shù)據(jù)進(jìn)行++操作;3.將工作內(nèi)存中的數(shù)據(jù)寫回到主內(nèi)存
        在多線程環(huán)境下volatile修飾的變量也是線程不安全的,要保證數(shù)據(jù)的安全性,我們還需要使用鎖機制
        解決:1.使用加鎖機制保障volatile修飾變量的原子性操作;2.原子類:AtomicInteger
    volatile可以防止指令重排序操作:
        為了提高性能,編譯器和處理器常常會對既定的代碼執(zhí)行順序進(jìn)行指令重排序;重排序的好處:可以提高處理的速度
###3.volatile內(nèi)存語義
    happens-before規(guī)則:
        如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,那么這兩個操作之間必須存在happens-before關(guān)系
        1.同一個線程中前面的所有寫操作對后面的操作可見
        2.鎖規(guī)則: 對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖
        3.volatile變量規(guī)則;4.傳遞性;5.start()規(guī)則;6.join()規(guī)則
    volatile寫讀建立的happens-before規(guī)則:如果線程1寫入了volatile變量v(臨界資源),接著線程2讀取了v,那么線程1寫入v及之前的寫操作都對線程2可見(線程1和線程2可以是同一個線程)
    volatile重排序規(guī)則小結(jié):寫volatile變量時,無論前一個操作是什么,都不能重排序;讀volatile變量時,無論后一個操作是什么,都不能重排序;當(dāng)先寫volatile變量,后讀volatile變量時,不能重排序;
###4.volatile高頻面試與總結(jié)
    long和double原子性:在32位系統(tǒng)環(huán)境無法一次讀取long類型數(shù)據(jù),多線程環(huán)境下對long變量的讀寫是不完整的 
    volatile在雙重檢查加鎖的單例中的應(yīng)用:
        單例模式:
            餓漢式單例:在獲取單例對象之前對象已經(jīng)創(chuàng)建完成了
                1.靜態(tài)變量:構(gòu)造器私有,定義一個靜態(tài)變量保存一個唯一的實例對象,提供一個方法返回單例對象; 2.靜態(tài)代碼塊
            懶漢式單例:在真正需要單例的時候才創(chuàng)建出該對象
                線程不安全的
                線程安全的:為獲取單例的方法加鎖,用synchronized,但是性能差
                volatile雙重檢查模式:
                    對象創(chuàng)建的步驟:a.分配內(nèi)存空間;b.調(diào)用構(gòu)造器,初始化實例;c.返回地址給引用; 底層可能進(jìn)行對象創(chuàng)建的重排序操作
                    靜態(tài)變量為什么要加volatile關(guān)鍵字?禁止指令重排序;保證可見性
                靜態(tài)內(nèi)部類單例
    volatile的使用場景:適合純賦值的操作;
                觸發(fā)器:可以作為刷新之前變量的觸發(fā)器,其他線程一旦發(fā)現(xiàn)該變量修改的值后,觸發(fā)獲取到的該變量之前的操作都將是最新的且可見
    votatile和synchronized
        區(qū)別:volatile只能修飾實例變量和類變量,而synchronized可以修飾方法,以及代碼塊
              volatile保證數(shù)據(jù)的可見性,但是不保證原子性(多線程進(jìn)行寫操作,不保證線程安全);而synchronized是一種排他(互斥)的機制
              volatile用于禁止指令重排序,可以解決單例雙重檢查對象初始化代碼執(zhí)行亂序問題
              volatile可以看成是輕量版的synchronized,volatile不保證原子性,但是如果是對一個共享變量進(jìn)行多個線程的賦值,而沒有其他的操作,那么就可以用volatile來代替synchronized,因為賦值本身是有原子性的,而volatile又保證了可見性,所有就可以保證線程安全了
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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