《Java後端知識體系》系列之併發關鍵字(volatile、synchronized)

三天沒有寫博客了,三天都在整理併發編程的知識,發覺真的太多了,放到一起肯定不好整理,就這樣分開整理,方便自己平時的閱讀!

在這裏插入圖片描述

併發關鍵字:

  • volitile

    • 特性
      • 可見性:在變量前面加了volatile的關鍵字就是指示JVM這個變量不穩定,所有線程更新它時都會立即同步到主存中,所有線程使用它的時候都會到主存中讀取,這樣保證了所有的線程使用該變量時都是最新的
        • 原理:volatile是從JVM層面保證變量的可見性。JVM中所有的變量存儲在主內存中,同時每個線程又有自己獨立的緩存用來存儲從主內存中緩存的數據,這樣每次獲取更新數據時更新緩存中的數據,然後再同步到主存中,這樣就減輕了主存的壓力,而volatile怎麼解決的可見性問題呢?首先爲了維持緩存以及主從中數據一致性的問題,volatile使用了JVM層面中的lock鎖,當一個volatile的變量更新時,會對該線程進行lock加鎖,同時也會讓其它線程中緩存的該變量都失效,當執行成功之後將該變量寫入主內存中,然後解除lock狀態,這樣每次一個更新操作都是立即同步到主存中的,而所有的讀取變量也都是通過該操作獲取到主存中最新的值。
      • 禁止重排序:對於使用volatile關鍵字的變量來說,volatile能夠保證它前面的操作都已經執行完畢並且返回的結果對後面的操作可見,後面的操作還沒有執行,能夠按照設定的順序執行一系列操作
        • 原理:volatile也是從JVM層面也保證有序性的。在程序中有很多的優化重排序:編譯器優化的重排序指令級的優化重排序內存系統的優化重排序,而volatile爲了解決防止這些重排序的發生,使用了內存屏障來解決排序問題,也就是在每個volatile操作的前後都加入一個內存屏障,來保證前後的執行都是按照順序執行的,實現如下:

          • 在每個volatile寫操作的前面插入一個StoreStore的屏障,同時在volatile寫操作的後面插入一個StoreLoad的屏障,
          • 在每個volatile讀操作的前面加入 LoadLoad屏障同時在每個volatile讀操作後面加入LoadStore屏障

          通過設定這樣的內存屏障來保證操作都是按照順序來執行的。

    • 用法:使用在變量
  • synchronized

    • 特性
      • **原子性:**一個或多個操作要麼全部執行成功要麼全部執行失敗。
      • **有序性:**線程的執行順序按照設定的順序執行。
      • **可見性:**一個線程對變量的修改其它線程能立即看到改變的值
      • **可重入性:**同一個線程可以獲取同一把鎖多次(wait、notify、notifyAll的作用)
    • 用法
      • 實例方法:在普通方法中使用synchronized關鍵字,作用的是實例對象。 synchronized都是作用在對象中的,只有作用在對象中,才能使用waitnotifynotifyAll來重新執行該線程。在實例方法中是通過ACC_SYNCHRONIZED這樣一個標誌,來告訴JVM這是一個同步方法,因此在進入該方法之前先要獲取相應的,然後鎖的計數器+1,執行結束後計數器-1,如果失敗就阻塞,直到該鎖被釋放,因此synchronized關鍵字作用於方法是都是通過ACC_SYNCHRONIZED來實現一個加鎖的操作的。

      • 靜態方法:在靜態方法中使用synchronized關鍵字,作用的是整個類對象。此時也是作用於方法中的,所以也是通過ACC_SYNCHRONIZED來實現的加鎖操作。

      • 代碼塊:在代碼塊中使用synchronized關鍵字,作用的是調用這個代碼塊的對象。在代碼塊中由monitorenter指令進入,然後monitorexit釋放鎖,在執行monitorenter之前需要嘗試獲取鎖,如果這個對象沒有被鎖定,或者當前線程已經擁有了這個對象的鎖,那麼就把鎖的計數器加1。當執行monitorexit指令時,鎖的計數器也會減1。當獲取鎖失敗時會被阻塞,一直等待鎖被釋放。
        但是反編譯的代碼中有兩個monitorexit,這是因爲第二個monitorexit是來處理異常的,正常情況下第一個monitorexit之後會執行goto指令,而該指令轉向的就是末尾的return,也就是說正常情況下只會執行第一個monitorexit釋放鎖,然後返回。而如果在執行中發生了異常,第二個monitorexit就起作用了,它是由編譯器自動生成的,在發生異常時處理異常然後釋放掉鎖。

      • **總結:**在JVM中,對象是分成三部分存在的:對象頭實例數據對齊填充

        • 對象頭:要結構是由Mark Word 和 Class Metadata Address 組成,其中Mark Word存儲對象的hashCode、鎖信息或分代年齡或GC標誌等信息,Class Metadata Address是類型指針指向對象的類元數據,JVM通過該指針確定該對象是哪個類的實例
        • 實例數據:存放類的屬性數據信息,包括父類的屬性信息,如果是數組的實例部分還包括數組的長度
        • 對齊填充:部分內存按4字節對齊;對其填充不是必須部分,由於虛擬機要求對象起始地址必須是8字節的整數倍,對齊填充僅僅是爲了使字節對齊
        • synchronized作用的是對象,這也是爲什麼等待和通知是在Object類中而不是在Thread中聲明的一個原因。

今天依然是會敲代碼的湯姆貓!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章