天天看點

可見性丶原子性和有序性

先說一下概念:

  • 可見性:

    一個線程對共享變量的修改,另外一個線程能夠立刻感覺到,我們稱為可見性;

  • 原子性:

    一個或者多個操作在 CPU 執行的過程中不被中斷的特性稱為原子性;

  • 有序性:

    指令重排導緻順序被打亂;

  線程工作記憶體: 是指 Cpu 的 ‘寄存器’ 和 ‘高速緩存’,線程的 working memory 是cpu的寄存器和高速緩存的抽象描述,資料讀取順序優先級 是:寄存器->高速緩存->記憶體

可見性:

線程工作空間導緻可見性問題

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-hNCfYp7P-1570757836145)(https://github.com/lantaoGitHub/photos/blob/master/Concurrency/%E5%85%B1%E4%BA%AB%E5%8F%98%E9%87%8F%E5%8F%AF%E8%A7%81%E6%80%A7.png?raw=true)]

  例如:線程A在主存中年将變量one=0拉去到自己的工作記憶體中,然後做了one = 5,當然這個操作是在cpu的寄存器中進行的,然後寫會高速緩存中,這時線程A的高速緩存還未執行同步主記憶體的操作,線程B又将one=0從主存拉取到了線程B的工作記憶體中,導緻A線程已經更新但是B線程看不到的可見性問題;

原子性:

線程切換導緻原子性問題 ++count

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-1NoRLBJh-1570757836147)(https://github.com/lantaoGitHub/photos/blob/master/Concurrency/%E7%BA%BF%E7%A8%8B%E5%88%87%E6%8D%A2%E5%AF%BC%E8%87%B4%E5%8E%9F%E5%AD%90%E6%80%A7%E9%97%AE%E9%A2%98++count%20.png?raw=true)]

  例如:當線程A從主記憶體中将共享變量Count加載到線程A的工作記憶體後,發生了線程切換,這個時候線程B也将共享變量Count從主記憶體加載到了線程B的工作記憶體,這時線程A和B的工作記憶體中count都是0,線程B執行了Count = Count + 1,然後寫回到主記憶體,這時候線程切換完成,回到了線程A再次執行 Count = Count + 1,再将線程A工作記憶體計算過的count寫回主記憶體,現在我們得到的主記憶體呢中Count值是1而不是2。

有序性:

指令重排導緻有序性問題;

在這裡講一個例子,就是擷取單例雙重檢查鎖(double-checked locking)判斷:

/**
 * @Auther: lantao
 * @Date: 2019-03-28 14:32
 * @Company: 随行付支付有限公司
 * @maill: [email protected]
 * @Description: TODO
 */
public class Test1 {
    
    private DoMain doMain;
    
    public DoMain getDoMain(){
        if(doMain == null){
            synchronized (this.getClass()){
                if(doMain == null){
                    doMain = new DoMain("");
                }
                return doMain;
            }
        }else{
            return doMain;
        }
    }
}

           

  在上邊的代碼中在synchronized内和外都有一個if判斷,判斷doMain是否為null操作,有很多人對synchronized中的if null判斷不了解,其實可以這樣想,線程A和線程B都執行到了synchronized這裡進行競争鎖,結果A得到鎖,判斷if null,結果還未執行個體化,繼續進行執行個體化,然後return對象并釋放鎖,這時線程B擷取到了鎖進入if null判斷,發現doMain已經被線程A執行個體化過了,直接傳回執行個體即可,第二個if null的作用就在這裡;

看上去上邊的代碼是完美的,但是new的操作上我們了解是:

  • 建立記憶體M
  • 在記憶體M上初始化doMain對象
  • 将記憶體M的位址指向變量doMain

但是實際上優化後(指令重排)的執行路徑可能是這樣的:

  • 建立記憶體M
  • 将記憶體M的位址指向變量doMain
  • 将記憶體M的位址指向變量doMain

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-W4Phn3Ou-1570757836147)(https://github.com/lantaoGitHub/photos/blob/master/Concurrency/%E6%9C%89%E5%BA%8F%E6%80%A7%E9%97%AE%E9%A2%98.png?raw=true)]

上圖大家應該都看的明白,我就不寫了,本文主要就是讓作者加深印象,本文是參考:https://time.geekbang.org/column/article/83682 總結的

可見性丶原子性和有序性

繼續閱讀