線程安全的單例模式


 一、餓漢式

  1、在static屬性中實例化(類加載的初始化階段實例化(在准備階段分配內存))

  2、在static代碼塊中實例化(類加載的初始化階段實例化)

  3、枚舉實現https://www.cnblogs.com/yangyongjie/p/11056454.html

 

 二、懶漢式

  1、同步方法或同步代碼塊

  2、雙重檢查鎖

    在Java多線程程序中,有時候需要采用延遲初始化來降低初始化類和創建對象的開銷,在使用這些對象時才進行初始化。延遲初始化需要注意線程安全

  問題,否則就容易出現問題。

    單例模式在獲取實例的方法中,若只判斷實例是否為null,是則創建對象,否則獲取對象。這種方法在多線程執行的時候必然會有線程安全問題。若獲取

  實例方法加synchronized關鍵字,則能實現線程同步解決線程安全問題,但是多線程下頻繁調用會造成巨大的性能開銷。

    1、雙重檢查鎖及其錯誤根源

      雙重檢查鎖是常見的延遲初始化技術,初衷是用它來降低同步的開銷,但是它是錯誤的用法。

    雙重檢查鎖代碼:

public class DoubleCheckLock {
    private static Instance instance;
    public static Instance getInstance(){
        // 第一次檢查
        if(instance==null){
            // 第一次檢查為null再進行加鎖,降低同步帶來的性能開銷
            synchronized (DoubleCheckLock.class){
                // 第二次檢查
                if(instance==null){
                    // 問題出在此處
                    instance=new Instance();
                }
            }
        }
        return instance;
    }
}

 

     錯誤根源:

      當第一次檢查時,讀取instance不為null時,instance引用的對象可能還沒有完成初始化!原因在於多線程下的重排序。

      instance=new Instance() new創建一個對象不是原子操作,分為三步:

        1)分配對象的內存空間

        2)初始化對象

        3)設置instance引用指向剛分配的內存地址

      但是在一些編譯器上(如JIT),2)和3)可能會發生重排序。

      Java規范保證了重排序不會改變單線程內的程序執行結果。但是在多線程下,若線程A在執行instance=new Instance();時發生了重排序,先執行了3),

    這時候線程B剛好獲取到了instance不為null,接着去訪問對象。但是這個時候線程A還未執行2),即還沒被線程A初始化,那么這個時候線程B得到的就是

    一個還沒有初始化的對象。

      解決方案:

        (1)不允許2)和3)重排序

        (2)允許2)和3)重排序,但不允許其他線程“看到”這個重排序

    

    2、基於volatile的解決方案

     通過將instance聲明為volatile型來禁止2)和3)之間的重排序

public class VolatileDoubleCheckLock {
    // 將instance聲明為volatile型
    private volatile static Instance instance;
    public static Instance getInstance(){
        // 第一次檢查
        if(instance==null){
            // 第一次檢查為null再進行加鎖,降低同步帶來的性能開銷
            synchronized (VolatileDoubleCheckLock.class){
                // 第二次檢查
                if(instance==null){
                    // 多線程下將禁止2)和3)之間的重排序
                    instance=new Instance();
                }
            }
        }
        return instance;
    }
}

 

    3、基於類初始化的解決方案

     JVM在類的初始化階段(即被Class加載后,且被線程使用之前),會執行類的初始化。在執行類的初始化期間,JVM會去獲取一個鎖(Class對象的初始化鎖)。這個鎖可以同步多個線程對一個類的初始化。

public class InstanceFactory {
    private static class InstanceHolder{
        public static Instance instance=new Instance();
    }
    
    public static Instance getInstance(){
        return InstanceHolder.instance;
    }
}

    在多線程下,第一個執行getInstance方法的線程會先初始化InstanceHolder類。多個線程的情況下也只有一個線程能獲取到InstanceHolder類的Class對象的初始化鎖。第一個獲取到nstanceHolder類的Class對象的初始化鎖的線程將初始化InstanceHolder類中的靜態屬性instance。根據happens-before關系,其他線程將知道InstanceHolder類已被初始化,將結束初始化過程直接訪問InstanceHolder。

 

    


免責聲明!

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



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