Java內存模型(JMM)

Java內存模型

  1. 概念
    內存模型(memory model):在特定的操作協議下,對特定的內存或高速緩存進行讀寫訪問的過程抽象。
  2. 作用
    java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。(注:此處變量與java編程中所說的變量有所區別,它包括了實例字段、靜態字段、構成數組對象的元素,但不包括局部變量和方法參數,因爲後者是線程私有的,不會被共享)
  3. 主內存和工作內存
    java內存模型規定了所有的變量都存儲在主內存中,每條線程擁有自己的工作內存(Working Memory),線程的工作內存中保存了被該線程使用到的變量的主內存副本的拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量。不同的線程之間也無法直接訪問其他線程的工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成。
  4. 區別
    上面所說的主內存/工作內存與java內存區域中java堆、棧、方法區等並不是同一個層次的內存劃分,這兩者基本沒有關係,如果一定要勉強對應,主內存主要對應於java堆中的對象實例數據部分,而工作內存則對應於虛擬機棧中的部分區域。
  5. 主內存和工作內存之間的操作
    虛擬機實現時必須保證下面提及的每一種操作都是原子的、不可再分的
    lock(鎖定):作用於主內存的變量,它把一個變量標識爲一條線程獨佔的狀態。
    unlock(解鎖):作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定。
    read(讀取):作用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用。
    load(載入):作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
    use(使用):作用於工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值的字節碼指令時將會執行這個操作。
    assign(賦值):作用於工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
    store(存儲):作用於工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便隨後的write操作使用。
    write(寫入):作用於主內存的變量,它把store操作從工作內存中得到的變量的值放入主內存變量中。
    注意:

    read/load和write/store
    不允許read和load、store和write操作之一單獨出現,即不允許一個變量從主內存讀取了但工作內存不接受,或者從工作內存發起回寫了但主內存不接受的情況出現。即java內存模型只要求上述兩個操作必須按順序執行,而沒有保證是連續執行,也就是說,read和load之間、store和write之間是可插入其他指令的。
    assign
    不允許一個線程丟棄它的最近的assign操作,即變量在工作內存中改變之後必須把該變化同步回主內存。
    不允許一個線程無原因地(沒有發生過任何assign操作)把數據從線程的工作內存同步回主內存中。
    lock/unlock


一個變量在同一時刻只允許一條線程對其進行lock操作,但lock操作可以被同一條線程重複執行多次,多次執行lock後,只有執行相同次數的unlock操作,變量纔會被解鎖。 如果對一個變量執行lock操作,那將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assign操作初始化變量的值。 對一個變量執行unlock操作之前,必須先把此變量同步回主內存中(執行store、write操作)

volatile 變量

  1. volatile
    關鍵字volatile可以說是java虛擬機提供的最輕量級的同步機制。
  2. volatile變量的兩種特性
    第一是保證此變量對所有線程的可見性,這裏的“可見性”是指當一條線程修改了這個變量的值,新值對於其他線程來說可以立即得知。由於Java裏面的運算並非原子操作,導致volatile變量的運算在併發下一樣是不安全的。
    第二使用volatile變量的第二個語義是禁止指令重排序優化。
//此處顯示了volatile並不能保證線程的安全性
public class Volatile12_1 {

    public static volatile int race = 0;
    public static void increase()
    {
        race++;
    }
    private static final int THREAD_COUNT = 20;
    public static void main(String[] args)
    {
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i = 0;i < THREAD_COUNT;i++)
        {
            threads[i] = new Thread(new Runnable(){

                @Override
                public void run() {
                    for(int i = 0;i < 10000;i++)
                        increase();
                }

            }); 
            threads[i].start();
        }
        while(Thread.activeCount() > 1)
        {
            Thread.yield();
        }
        System.out.println(race);

    }
}
  1. 在不符合以下兩種情況下,需要加鎖來保證原子性
    由於volatile變量只能保證可見性,不符合以下兩條規則的運算場景中,需要通過加鎖(使用synchronized或java.util.concurrent中的原子類)來保證原子性。
    第一運算結果並不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值。
    第二變量不需要與其他的狀態變量共同參與不變約束。
  2. 總結
    對於volatile型變量的特殊規則,線程對變量的use動作可以認爲是和線程對變量的load、read動作相關聯,必須連續一起出現,即每次使用volatile變量的時候,都必須先從主內存刷新最新的值,用於保證能看見其他線程對變量所做的修改後的值。
    線程對變量的assign動作可以認爲是和線程對變量的store、write動作關聯,必須連續一起出現(即要求在工作內存中,每次修改變量後必須立刻同步回主內存中,用於保證其他線程可以看到自己對變量所做的修改)
    1. 普通變量和volatile變量之間的區別
      volatile的特殊規保證了新值能立即同步到主內存,以及每次使用前立即從主內存刷新。因此volatile保證了多線程操作時變量的可見性,而普通變量則不能保證這一點。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章