多執行緒中單例模式的優化
- 2020 年 1 月 21 日
- 筆記
單例模式
在編程中,單例模式是我們常用的一種設計模式,功能是保證在整個系統只用一個該對象的對象,具體程式碼如下:
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); return singleton; } return singleton; } }
上面的程式碼我們知道並不是執行緒安全的,在多執行緒環境下,容易造成創建多個對象。 測試程式碼如下:
@Test public void testSingleton() throws InterruptedException { for (int i=0;i<10;i++){ new Thread(()->{ Singleton.getInstance(); }).start(); } Thread.currentThread().join(); }
運行結果如下:
創建對象 創建對象 創建對象 創建對象 創建對象 創建對象 創建對象
解決方案
對於上面的問題解決的方法有很多,比如使用加鎖的方式,double檢測的方式,為了驗證最有方案我們把程式碼修改下:
public class Singleton { private static Singleton singleton; private Singleton() { try { Thread.sleep(10);//增加創建對象的耗時 } catch (Exception e) { } } public static Singleton getInstance() { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); return singleton; } } return singleton; } }
測試程式碼:
@Test public void testSingleton() throws InterruptedException { long start=System.currentTimeMillis(); List<Thread> threadList = new ArrayList<>(); for (int i = 0; i < 1000; i++) { Thread thread = new Thread(() -> { Singleton.getInstance(); }); threadList.add(thread); thread.start(); } for (Thread t : threadList) { t.join(); } long end=System.currentTimeMillis(); System.out.println("運行耗時:"+(end-start)); }
方案一:使用synchronized 關鍵字
public static Singleton getInstance() { synchronized (Singleton.class){ if (singleton == null) { System.out.println("創建對象"); singleton = new Singleton(); } } return singleton; }
經過多次測試時間維持在410ms左右,下面是一次測試結果
運行耗時:410
這個雖然成功的保證了只有一個對象,但同樣也會把其他的執行緒阻塞在創建的鎖的前面,造成了性能上面的開銷,如果創建一個對象的時間比較長,這個性能的開銷是相當可觀的。
方案二:double驗證
public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); return singleton; } } } return singleton; }
運行耗時:380
上面的程式碼雖然聰明的避開的過多執行緒等待的原因,但是徹底消除執行緒排隊的現象,因為創建對象分需要耗時,這樣就給其他執行緒提供了「可乘之機」
方案三:使用volatile共享變數 (最優方案)
共享變數是執行緒間同步的「輕量級鎖」,徹底消除執行緒排隊的現象,此處用於單例模式的設計,能夠實現最小性能的開銷:
private volatile static Singleton singleton;
運行耗時:280