單例模式的應用場景就再也不敘述了,相信網上一找一大把。主要說一說單例模式的線程安全問題。單例模式主要分爲兩種實現:餓漢模式和懶漢模式。安全
餓漢模式是指,項目一旦被啓動,該類就會被加載完成,後續不在須要建立,整個項目的運行過程當中就只有一個實例。實現以下所示:多線程
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(); } }
運行結果以下圖所示:
此時,爲確保懶漢模式狀況下避免發生線程安全問題,咱們就須要使用到同步鎖,如代碼中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(); } }
運行結果以下圖所示:
線程
餓漢模式:由於在類加載是就已經建立了實例,後續不須要在建立實例,自然地避開了線程安全問題。
懶漢模式:由於是在使用時才進行實例化操做,因此存在着線程安全問題,此時須要經過加同步鎖來確保多線程安全,避免屢次實例化。code