【Java併發程式設計學習筆記】volatile關鍵字
阿新 • • 發佈:2021-10-02
volatile解決可見性和有序性,無鎖化程式設計,DCL問題,CAS
synchronized&volatile
volatile關鍵字
volatile關鍵字是JVM提供的一個輕量級的同步機制,可以保證有序性和可見性,不能保證原子性。
volatile原理
volatile修飾的變數帶有記憶體屏障(寫屏障/讀屏障),Mermory Barrier(Mermory Fence)
volatile保證可見性和有序性
可見性
volatile修飾的變數是帶有寫屏障和讀屏障的,寫屏障之前對於共享變數的修改都重新整理到主存中。在讀屏障之後,讀取的資料都是從主存中載入進來的最新資料,每次讀取的時候都從主存讀取出來生成一個新的副本。
public class Main { static volatileboolean 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問題
- 新增版本號,把資料更新到主存的時候,再次讀取主存中的變數的版本號,如果現在變數的版本號與主存中一致則更新。
- 新增時間戳。