設計模式|單例模式的懶漢式爲什麼是線程不安全的,懶漢式如何實現線程安全

目錄

懶漢式爲什麼是線程不安全的

懶漢模式實現線程安全

1.對實例化方法加鎖

2.double check+volatile方案


單例模式是工作中高頻使用的設計模式之一。單例模式可以確保內存中單例類只有一個實例,有效的減少了內存的開銷,避免了類的重複創建和銷燬。

懶漢式爲什麼是線程不安全的

單例模式有兩種實現方式,即大家所熟悉的餓漢式和懶漢式。二者的區別是創建實例的時機,餓漢式在應用啓動時就創建了 實例,餓漢式是線程安全的,是絕對單例的。懶漢式在對外提供的獲取方法被調用時會實例化對象。在多線程情況下,懶漢模式不是線程安全的,示範代碼如下:

public class LazySingleTon {
    private static  LazySingleTon lazySingleTon = null;
    private LazySingleTon(){
    }
    public static LazySingleTon getInstance(){
        if(lazySingleTon == null){
            //對下面的代碼加斷點    
            lazySingleTon = new LazySingleTon();
        }
        System.out.println(lazySingleTon);
        return lazySingleTon;
    }

    public static void main(String[] args) {
        for(int i = 1 ; i <= 2 ;i ++ ){
            Thread thread = new ForTestThread();
            thread.start();
        }
    }
 }
public class ForTestThread extends Thread {
    @Override
    public void run() {
        LazySingleTon lazySingleTon = LazySingleTon.getInstance();
    }
}

操作如下

配合idea的多線程debug,執行main方法,在實例化處加斷點。當第一個線程執行驗證了lazySingleTon對象爲null,準備new一個新對象時第二個線程也驗證完了lazySingleTon爲null,這時兩個線程都會實例化LazySingleTon,執行完成輸出的內容:

Connected to the target VM, address: '127.0.0.1:49751', transport: 'socket'
com.rbac.console.singleton.LazySingleTon@492dccc0
Disconnected from the target VM, address: '127.0.0.1:49751', transport: 'socket'
com.rbac.console.singleton.LazySingleTon@2f17749c

打印內容顯示同一個內存中存在兩個實例分別是:LazySingleTon@2f17749c、LazySingleTon@492dccc0。這表明在多線程情況下有一定機率會出現一個單例類會有多個實例,證明懶漢式是非線程安全的。

懶漢模式實現線程安全

1.對實例化方法加鎖

 public static synchronized  LazySingleTon getInstance(){
        if(lazySingleTon == null){
            lazySingleTon = new LazySingleTon();
        }
        System.out.println(lazySingleTon);
        return lazySingleTon;
    }

加鎖可以確保實例話方法只能有一個線程執行,確保兩個線程不會同時進入實例化方法。缺點是同步鎖的加鎖和解鎖比較消耗資源,而且synched關鍵字修飾static方法時鎖的是整個class,對性能會有影響

2.double check+volatile方案

public static  LazySingleTon getInstance(){
        if(lazySingleTon == null){
            synchronized (LazySingleTon.class) {
                if (lazySingleTon == null) {
                    //1.分配內存
                    //2.初始化對象
                    //3.設置LazySingleTon類執行內存
                    lazySingleTon = new LazySingleTon();
                }
            }
        }
        System.out.println(lazySingleTon);
        return lazySingleTon;
    }

double check方案可以實現線程安全,且降低內存消耗。代碼中三行註釋代表了new對象時底層進行了三步操作,由於JVM優化算法可能會指令重排,也就是第二步和第三步執行的順序會互換。這樣可能會出現第一個第一個線程出現了指令重排情況,還沒來得及初始化對象,第二個線程就進行了對象獲取,導致獲取到的對象是null。

爲避免這種情況的發生可以使用volatile關鍵字修飾靜態類,禁止指令重排

    private volatile static  LazySingleTon lazySingleTon = null;

也可以使用靜態內部類的初始化延遲加載解決,靜態內部類有初始化鎖,達到線程安全的目的

public class LazySingleTon {
    private LazySingleTon(){
    }
    public static  LazySingleTon getInstance(){
        return  InnerClass.lazySingleTon;
    }

    private static class InnerClass{
        private  static LazySingleTon lazySingleTon = new LazySingleTon();
    }

    public static void main(String[] args) {
        for(int i = 1 ; i <= 2 ;i ++ ){
            Thread thread = new ForTestThread();
            thread.start();
        }
    }
 }

文章內容參考自慕課網

設計模式學習友情鏈接:

序列化、反序列化對單例的破壞、原因分析、解決方案及解析

設計模式【創建型模式】


                                                          這位小可愛,如果覺得文章不錯,請關注或點贊    (-__-)謝謝

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章