上章節我們在懶漢式的單例模式上解決了多線程安全的問題,但解決問題的同時,新的問題也隨之而來。
上節問題:
1、在靜態方法(static)上添加關鍵字(synchronized同步鎖),就是相當於在類上加鎖,鎖的范圍大,損耗性能。
2、加鎖、解鎖過程消耗資源。
那么,我們該如何解決呢?
1 public class lazyDoubleCheckSingleton { 2 3 private static lazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; 4 5 private lazyDoubleCheckSingleton() { 6 7 } 8 public static lazyDoubleCheckSingleton getInstance() { 9 if (lazyDoubleCheckSingleton == null) { 10 synchronized (lazyDoubleCheckSingleton.class){ 11 if (lazyDoubleCheckSingleton == null){ 12 lazyDoubleCheckSingleton = new lazyDoubleCheckSingleton();
//1、分配內存給這個對象
//2、初始化對象
//3、設置lazyDoubleCheckSingleton,指向剛分配的內存地址
13 } 14 } 15 } 16 return lazyDoubleCheckSingleton; 17 } 18 }
此種方法就是懶漢模式的雙重檢測式,把鎖加在方法里面,只有空的話才會加鎖,不為空的話,直接return lazyDoubleCheckSingleton,大大節省了開銷,但是這段代碼,還存在着兩大隱患,分別是9行、12行,首先在9行判斷了是否為空,有可能是不為空的,他雖然不為空,但是是在12行還沒完成初始化的情況下,12行的代碼雖然是一行,確實經歷了三個步驟,注釋上2和3的順序可能調換,進行重排序,也就是先指向內存地址,但是還沒初始化對象。下面給大家開一個圖。

這是單線程,介入多線程呢?

看吧,就出問題了呢,想要解決這個問題,有兩種解決方案
1、不允許2、3進行重排序,加入了volatile關鍵字.
2、允許一個線程進行重排序,但不允許另外線程看到他的重排序。
那么我們看看用第一種方案是怎么解決的呢?
/** * Created by sww_6 on 2019/4/10. * 雙重檢查 * 1、不允許2、3進行重排序,加入了volatile關鍵字. * 2、允許一個線程進行重排序,但不允許另外線程看到他的重排序。 * 划重點: * 1、在多線程的時候,cpu有共享內存。 * 2、加入了volatile關鍵字之后,所有線程都能看到共享內存的最新狀態,保證內存可見性。 * <p> * 怎么保持內存一致性? * 用volatile修飾的共享變量,在進行寫操作的時候,會多出來很多匯編代碼,起到兩個作用。 * 1、是將當前處理器緩存行的數據寫回到系統內存,寫回內存的操作,會使在其他cpu里緩存了該內存的數據無效,其他cpu緩存的數據無效了,就會從共享內存同步數據。保證了內存的可見性。 * 主要使用的是緩存一致性協議 * <p> * 優點: * 既兼顧了性能,又兼顧了線程安全。 */ public class lazyDoubleCheckSingleton { private volatile static lazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; private lazyDoubleCheckSingleton() { } public synchronized static lazyDoubleCheckSingleton getInstance() { if (lazyDoubleCheckSingleton == null) { synchronized (lazyDoubleCheckSingleton.class) { if (lazyDoubleCheckSingleton == null) { lazyDoubleCheckSingleton = new lazyDoubleCheckSingleton(); } } } return lazyDoubleCheckSingleton; } }
好了,我們下期見!
