設計模式之單例模式(線程安全)

單例模式

單例模式的應用場景就再也不敘述了,相信網上一找一大把。主要說一說單例模式的線程安全問題。單例模式主要分爲兩種實現:餓漢模式和懶漢模式。安全

餓漢模式

餓漢模式是指,項目一旦被啓動,該類就會被加載完成,後續不在須要建立,整個項目的運行過程當中就只有一個實例。實現以下所示:多線程

public class Singleton {

    private Singleton () {
        System.out.println("我被建立了!");
    }

    /**
     * 餓漢模式,類加載時就會建立實例
     */
    private static Singleton instance = new Singleton();

    @PostConstruct
    public void init() {
        System.out.println("啥時候");
    }

    public static Singleton getInstance() {
        return instance;
    }
}

該實例在類被加載時就已經建立,後續使用的都是同一個實例,不會再次建立,因此是線程安全的。也就是說單例模式的餓漢模式自然就不存在線程安全問題。併發

懶漢模式

懶漢模式與餓漢模式的最大區別就是,懶漢模式是在該類須要被使用的時候纔會建立實例對象,而不是系統啓動時加載類就建立。實現以下所示:單元測試

public class LazySingleton {

    private LazySingleton() {
        //看看是否會屢次建立
        System.out.println("我被建立了!");
    }

    /**
     * 懶漢模式,使用時纔會建立實例
     */
    private static LazySingleton instance = null;

    //線程安全
    public static LazySingleton getInstance1() {
        try {
            //經過休眠,模擬併發狀況
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                    return instance;
                }
            }
        }
        return instance;
    }

    //線程不安全
    public static LazySingleton getInstance2() {
        try {
            //經過休眠,模擬併發狀況
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (instance == null) {
            instance = new LazySingleton();
            return instance;
        }
        return instance;
    }
}

如代碼中所示,getInstance2 就是正常狀況下的懶漢模式實現,咱們經過休眠來模擬併發狀況,在單元測試時,發現該類被實例化了2次,說明這種實如今單線程是沒問題的,可是在多線程時就會發生線程安全問題。測試

@Test
public void singleTest() {
    for (int i =0; i<100; i++) {
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
    }
}

運行結果以下圖所示:
image.png
此時,爲確保懶漢模式狀況下避免發生線程安全問題,咱們就須要使用到同步鎖,如代碼中getInstance1 所示,咱們爲實例化的操做進行加鎖,同時進行double check 操做,以免屢次實例化。固然避免線程安全問題的一種解決方法,還有其餘方法,就不在此贅述。在加上同步鎖以後,咱們再進行單元測試時,發現果真只建立了一次實例,完美地解決了線程安全問題。spa

@Test
public void singleTest() {
    for (int i =0; i<100; i++) {
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
    }
}

運行結果以下圖所示:
image.png線程

總結

餓漢模式:由於在類加載是就已經建立了實例,後續不須要在建立實例,自然地避開了線程安全問題。
懶漢模式:由於是在使用時才進行實例化操做,因此存在着線程安全問題,此時須要經過加同步鎖來確保多線程安全,避免屢次實例化。code

相關文章
相關標籤/搜索