1. 程式人生 > 其它 >【Java併發程式設計學習筆記】volatile關鍵字

【Java併發程式設計學習筆記】volatile關鍵字

volatile解決可見性和有序性,無鎖化程式設計,DCL問題,CAS

volatile關鍵字

volatile關鍵字是JVM提供的一個輕量級的同步機制,可以保證有序性和可見性,不能保證原子性。

volatile原理

volatile修飾的變數帶有記憶體屏障(寫屏障/讀屏障),Mermory Barrier(Mermory Fence)

volatile保證可見性和有序性

可見性

volatile修飾的變數是帶有寫屏障和讀屏障的,寫屏障之前對於共享變數的修改都重新整理到主存中。在讀屏障之後,讀取的資料都是從主存中載入進來的最新資料,每次讀取的時候都從主存讀取出來生成一個新的副本。

public class Main {
    static volatile
boolean flag = true; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { public void run() { // 讀屏障,在此之後從主存之中讀取共享變數的最新值 while (flag) { } } }); Thread.sleep(
2000); t1.start(); Thread t2 = new Thread(new Runnable() { public void run() { flag = false; // 寫屏障,在此之前將共享變數的值重新整理到主存中 } }); t2.start(); } }

有序性

volatile遵循happened-befores規則:多執行緒情況下,即便發生了指令重排序也不影響最終的結果。

synchronized&volatile

  • volatile關鍵字是執行緒同步的輕量級實現,效能要比synchronized
  • volatile只能修飾變數;synchronized可以修飾方法和同步程式碼塊
  • JDK新版本的釋出優化了synchronized,優化後的synchronized執行效率有了很大的提升,一般在開發中使用synchronized
  • 多執行緒反問volatile不會阻塞,synchronized會阻塞
  • volatile不能保證原子性,可以保證可見性和有序性;synchronized三個核心問題都可以解決
  • volatile解決的是多個執行緒之間共享資料的可見性;synchronized解決的是多執行緒之間訪問共享資源的同步性

DCL+volatile

// Double Check Lock
public class Singleton {
    //private static volatile Singleton instance;
    private static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    // new不是原子操作,所以有可能引發多執行緒問題
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
    }
}
  • new操作不是原子操作,這一點我們可以通過檢視位元組碼檔案來得知
  • 檢視位元組碼檔案:idea安裝jclasslib外掛,view->show Bytecode with jclasslib
// 指令含義檢視位元組碼手冊
17 new #3 <Singleton> 
// 1. 在堆區分配記憶體,生成一個不完全的物件,
// 將不完全物件的引入壓入棧頂
20 dup 
// 1. 複製棧頂元素, 將複製陣列壓入棧頂
21 invokespecial #4 <Singleton.<init> : ()V>
// 堆區中的物件就是一個完整的物件了(執行了預設構造方法)
24 putstatic #2 <Singleton.instance : LSingleton;>
// 將完整物件的引用賦值給方法區的共享變數

正常流程(上面的流程) // 1.生成一個不完全物件(建立一個物件空間) // 2.初始化物件 // 3.instance指向初始化物件 // 4.單執行緒訪問物件沒有問題
如果發生指令重排 // 1.首先建立一個不完全物件(建立一個物件空間) // 2.instance指向這個物件空間 // 3.初始化物件 // 4.單執行緒訪問物件的時候不會影響結果 // 問題:多執行緒情況下,可能在當前執行緒在建立物件的過程中, // 由於指令重排序使得其他的執行緒獲取到instance==null,此時instance只對自己的執行緒可見, // 那麼競爭到鎖以後,instance依然為null,進而無法保證得到的物件是單例的,此時就需要新增volatile

CAS

  • Unsafe實現了CAS操作(Java的原子操作類中使用了Unsafe)。
  • Java程式碼中,Unsafe類只能通過反射來獲取。
  • CAS可以將read-modify-write這類的操作轉化為原子操作。
  • 悲觀鎖:synchronized可以稱為悲觀鎖,每次只允許一個執行緒執行同步程式碼塊,其他執行緒只能阻塞。
  • 樂觀鎖:CAS可以稱為樂觀鎖,允許多個執行緒同步操作共享資源。

CAS+volatile

  • CAS+volatile可以實現無鎖化程式設計
    • CAS在操作共享變數的時候,如果使用volatile修飾共享變數,可以保證共享變數的可見性。
    • 無鎖化程式設計適用於競爭不激烈,多核CPU的情況之下:
    • 執行緒可以併發,不會進入阻塞或者等待狀態,可以提高執行的效率;
    • 當時當競爭激烈的時候,那麼比較的次數就會變多,重試的次數也會變多,CPU佔用也就越多,反而影響效率。

CAS解決ABA問題

  • 新增版本號,把資料更新到主存的時候,再次讀取主存中的變數的版本號,如果現在變數的版本號與主存中一致則更新。
  • 新增時間戳。