在多線程環境中,volatile能保證共享變量的可見性以及一定程度的有序性。單例模式有多種寫法,有線程安全的和非線程安全的,有懶漢式和餓漢式,有利用static關鍵字修飾變量、方法、代碼塊、內部類的實現,還有用枚舉實現的,今天我們討論下單例模式里面較為復雜的double check寫法,先看下代碼:
1 public class Singleton{ 2 private static volatile Singleton instance = null; 3 4 private Singleton(){} 5 6 public Singleton getInstance(){ 7 if(null == instance){ 8 synchronized(Singleton.class){ 9 if(null == instance){ 10 instance = new Singleton(); 11 } 12 } 13 } 14 return instance; 15 } 16 }
- 為什么需要兩次check?
- 如果沒有外層的check,相當於給整個getInstance()方法加上了synchronized關鍵字,也就是每次獲取單例對象都要獲取class對象的monitor,monitor是粒度較大的的鎖,開銷較大。所以外層的判斷目的是:第一次獲取單例對象后,再次獲取該單例對象無需進行同步
- 如果沒有內層的check,假如有兩個線程,線程1和線程2同時進入外層判斷,即第8步,線程1獲得對象鎖,進入同步代碼塊並初始化對象后,釋放對象鎖,返回單例對象結束了,線程1獲取對象鎖進入同步代碼塊后又再次初始化了instance對象,導致多線程下單例模式的非線程安全;
- 為什么instance實例需要加volatile關鍵字?
- 因為volatile的禁止指令重排序,在第10步中,初始化instance對象並非原子操作,它包括:1.開辟堆內存2.調用構造方法初始化對象3.將instance指向新對象;如果沒有volatile關鍵字,且在並發情況下,如果某個線程完成了1 2兩個步驟,還未給instance變量賦值,此時另一個線程進入外層判空后后發現instance對象非空,就返回了未構造完全的instance對象,導致空指針異常;volatile的意義在於能夠禁止對當前對象進行指令的重排序,也就是happen-before原則的關於volatile的一條:"volatile變量規則:對一個變量的寫操作happen before於后面對這個變量的讀操作",也就是說,無論什么情況,對於volatile變量的寫操作必須在完成后才能讀取,不能暴露寫操作的中間狀態。所以不會出現未完成構造就讀取的情況;但是volatile不能保證同時對變量的寫操作也是有序的,也就是volatile不能保證原子性
happen before:描述了線程安全的可見性,有很多規則,關於volatile的規則是:對一個變量的寫操作的結果,對這個變量的讀操作可見
參考鏈接:
- https://www.cnblogs.com/dolphin0520/p/3920373.html
- https://stackoverflow.com/questions/7855700/why-is-volatile-used-in-this-example-of-double-checked-locking
- https://blog.csdn.net/DL88250/article/details/5439024
- https://www.cnblogs.com/dolphin0520/p/3613043.html
