Java記憶體模型與volatile關鍵字
Java記憶體模型(Java Memory Model)
Java記憶體模型(JMM),不同於Java執行時資料區,JMM的主要目標是定義程式中各個變數的訪問規則,即在虛擬機器中將變數儲存到記憶體和從記憶體中讀取資料這樣的底層細節。JMM規定了所有的變數都儲存在主記憶體中,但每個執行緒還有自己的工作記憶體,執行緒的工作記憶體中儲存了被該執行緒使用到的變數的主記憶體副本拷貝。執行緒對變數的所有操作都必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變數,工作記憶體是執行緒之間獨立的,執行緒之間變數值的傳遞均需要通過主記憶體來完成。
Volatile關鍵字
平時在閱讀jdk原始碼的時候,經常看到原始碼中有寫變數被volatile關鍵字修飾,但是卻不是十分清除這個關鍵字到底有什麼用處,現在終於弄清楚了,那麼我就來講講這個volatile到底有什麼用吧。
當一個變數被定義為volatile之後,就可以保證此變數對所有執行緒的可見性,即當一個執行緒修改了此變數的值的時候,變數新的值對於其他執行緒來說是可以立即得知的。可以理解成:對volatile變數所有的寫操作都能立刻被其他執行緒得知。但是這並不代表基於volatile變數的運算在併發下是安全的,因為volatile只能保證記憶體可見性,卻沒有保證對變數操作的原子性。比如下面的程式碼:
/** * 發起20個執行緒,每個執行緒對race變數進行10000次自增操作,如果程式碼能夠正確併發, * 則最終race的結果應為200000,但實際的執行結果卻小於200000。 * * @author Colin Wang * */ public class VolatileTest { public static volatile int race = 0; public static void increase() { race++; } private static final int THREADS_COUNT = 20; public static void main(String[] args) { Thread[] threads = new Thread[THREADS_COUNT]; for (int i = 0; i < THREADS_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); } }
這便是因為race++操作不是一個原子操作,導致一些執行緒對變數race的修改丟失。若要使用volatale變數,一般要符合以下兩種場景:
- 變數的運算結果並不依賴於變數的當前值,或能夠保證只有單一的執行緒修改變數的值。
- 變數不需要與其他的狀態變數共同參與不變約束。
使用volatile變數還可以禁止JIT編譯器進行指令重排序優化,這裡使用單例模式來舉個例子:
/** * 單例模式例程一 * * @author Colin Wang * */ public class Singleton_1 { private static Singleton_1 instance = null; private Singleton_1() { } public static Singleton_1 getInstacne() { /* * 這種實現進行了兩次instance==null的判斷,這便是單例模式的雙檢鎖。 * 第一次檢查是說如果物件例項已經被建立了,則直接返回,不需要再進入同步程式碼。 * 否則就開始同步執行緒,進入臨界區後,進行的第二次檢查是說: * 如果被同步的執行緒有一個建立了物件例項, 其它的執行緒就不必再建立例項了。 */ if (instance == null) { synchronized (Singleton_1.class) { if (instance == null) { /* * 仍然存在的問題:下面這句程式碼並不是一個原子操作,JVM在執行這行程式碼時,會分解成如下的操作: * 1.給instance分配記憶體,在棧中分配並初始化為null * 2.呼叫Singleton_1的建構函式,生成物件例項,在堆中分配 * 3.把instance指向在堆中分配的物件 * 由於指令重排序優化,執行順序可能會變成1,3,2, * 那麼當一個執行緒執行完1,3之後,被另一個執行緒搶佔, * 這時instance已經不是null了,就會直接返回。 * 然而2還沒有執行過,也就是說這個物件例項還沒有初始化過。 */ instance = new Singleton_1(); } } } return instance; } }
/** * 單例模式例程二 * * @author Colin Wang * */ public class Singleton_2 { /* * 為了避免JIT編譯器對程式碼的指令重排序優化,可以使用volatile關鍵字, * 通過這個關鍵字還可以使該變數不會在多個執行緒中存在副本, * 變數可以看作是直接從主記憶體中讀取,相當於實現了一個輕量級的鎖。 */ private volatile static Singleton_2 instance = null; private Singleton_2() { } public static Singleton_2 getInstacne() { if (instance == null) { synchronized (Singleton_2.class) { if (instance == null) { instance = new Singleton_2(); } } } return instance; } }
變數在有了volatile修飾之後,對變數的修改會有一個記憶體屏障的保護,使得後面的指令不能被重排序到記憶體屏障之前的位置。volalite變數的讀效能與普通變數類似,但是寫效能要低一些,因為它需要插入記憶體屏障指令來保證處理器不會發生亂序執行。即便如此,大多數場景下volatile的總開銷仍然要比鎖低,所以volatile的語義能滿足需求時候,選擇volatile要優於使用鎖。
相關文章
- Java記憶體模型——volatile關鍵字Java記憶體模型
- java記憶體模型及volatile關鍵字Java記憶體模型
- 深入理解Java記憶體模型JMM與volatile關鍵字Java記憶體模型
- 全面理解Java記憶體模型(JMM)及volatile關鍵字Java記憶體模型
- Java併發程式設計:JMM (Java記憶體模型) 以及與volatile關鍵字詳解Java程式設計記憶體模型
- Java多執行緒-帶你認識Java記憶體模型,記憶體分割槽,從原理剖析Volatile關鍵字Java執行緒記憶體模型
- 併發程式設計之 Java 記憶體模型 + volatile 關鍵字 + Happen-Before 規則程式設計Java記憶體模型APP
- Volatile之Java記憶體模型概念Java記憶體模型
- 10-Java中共享記憶體可見性以及synchronized和volatile關鍵字Java記憶體synchronized
- Java volatile關鍵字作用Java
- Java volatile關鍵字解析Java
- Java關鍵字volatile的理解Java
- Java記憶體模型(MESI、記憶體屏障、volatile和鎖及final記憶體語義)Java記憶體模型
- java併發之volatile關鍵字Java
- Java併發—— 關鍵字volatile解析Java
- Volatile關鍵字
- volatile 關鍵字
- 深入瞭解 Java 的 volatile 關鍵字Java
- volatile關鍵字解析
- Volatile關鍵字剖析
- 一個具體的例子學習Java volatile關鍵字Java
- 【Java併發】1. Java執行緒記憶體模型JMM及volatile相關知識Java執行緒記憶體模型
- Volatile關鍵字與執行緒安全執行緒
- Java記憶體模型FAQ(十)volatile是幹什麼用的Java記憶體模型
- Java併發程式設計volatile關鍵字Java程式設計
- java多執行緒4:volatile關鍵字Java執行緒
- 深入彙編指令理解Java關鍵字volatileJava
- 深入理解Java中的volatile關鍵字Java
- java併發程式設計:volatile關鍵字Java程式設計
- Java多執行緒(二)volatile關鍵字Java執行緒
- java併發程式設計——volatile關鍵字Java程式設計
- volatile關鍵字淺析
- 深入解析volatile關鍵字
- 快速理解 volatile 關鍵字
- Java記憶體模型Java記憶體模型
- Java 記憶體模型Java記憶體模型
- JVM記憶體結構、Java記憶體模型和Java物件模型JVM記憶體Java模型物件
- Java併發程式設計:volatile關鍵字解析Java程式設計
- Java面試題集錦(1):volatile關鍵字Java面試題