單例模式的double check寫法中的volatile關鍵字


在多線程環境中,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 }
  1. 為什么需要兩次check?
    1. 如果沒有外層的check,相當於給整個getInstance()方法加上了synchronized關鍵字,也就是每次獲取單例對象都要獲取class對象的monitor,monitor是粒度較大的的鎖,開銷較大。所以外層的判斷目的是:第一次獲取單例對象后,再次獲取該單例對象無需進行同步
    2. 如果沒有內層的check,假如有兩個線程,線程1和線程2同時進入外層判斷,即第8步,線程1獲得對象鎖,進入同步代碼塊並初始化對象后,釋放對象鎖,返回單例對象結束了,線程1獲取對象鎖進入同步代碼塊后又再次初始化了instance對象,導致多線程下單例模式的非線程安全;
  2. 為什么instance實例需要加volatile關鍵字?
  3. 因為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的規則是:對一個變量的寫操作的結果,對這個變量的讀操作可見

參考鏈接:

  1. https://www.cnblogs.com/dolphin0520/p/3920373.html
  2. https://stackoverflow.com/questions/7855700/why-is-volatile-used-in-this-example-of-double-checked-locking
  3. https://blog.csdn.net/DL88250/article/details/5439024
  4. https://www.cnblogs.com/dolphin0520/p/3613043.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM