多執行緒中單例模式的優化

  • 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