1. 程式人生 > >單例模式之懶漢單例(延遲初始化)多執行緒再解析

單例模式之懶漢單例(延遲初始化)多執行緒再解析

單例模式之懶漢單例(延遲初始化)多執行緒再解析

1、多執行緒下的懶漢單例:
	public class Lazysingleton {
		private static Lazysingleton m_instance = null;

		// 私有預設構造方法,外界無法直接例項化
		private Lazysingleton() {
		}

		// 靜態工廠方法
		public static Lazysingleton getInstance() throws InterruptedException {
			// 延遲載入
			if (m_instance == null) {
				// 模擬建立物件的準備工作
				Thread.sleep(3000);
				m_instance = new Lazysingleton();// 初始化這個單例
			}
			return m_instance;
		}
	}
	public class MyThread extends Thread {

		@Override
		public void run() {
			try {
				System.out.println(Lazysingleton.getInstance().hashCode());
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public class TestLazy1 {

		public static void main(String[] args) {
			MyThread t1=new MyThread();
			MyThread t2=new MyThread();
			MyThread t3=new MyThread();
			
			t1.start();
			t2.start();
			t3.start();
		}
	}
    
列印結果說明創建出來三個物件,並不是單例,多執行緒下懶漢單例是非執行緒安全的。

多執行緒下單例模式非執行緒安全的解決方案:


1、宣告synchronized關鍵字,實現同步方法

加入同步方法得到相同例項物件,此方法執行效率非常低,是同步執行,下一個執行緒想要取得物件,必須等上一個執行緒釋放鎖後,才能執行。


2、使用同步程式碼塊

與使用synchronized同步方法一樣是同步執行,效率非常低。得到相同例項物件。

3、部分程式碼上鎖,進行單獨同步,非執行緒安全


4、使用DCL雙重檢查鎖定


使用DCL雙重檢查鎖定成功解決懶漢模式的多執行緒問題,DCL也是大多數多執行緒結合單例模式使用的解決方案。

DCL是常見的延遲初始化技術,但有一個錯誤的用法。使用DCL需要一些技巧。

存在錯誤的根源:

a.多執行緒試圖在同一時間建立物件,會通過加鎖來保證只有一個執行緒建立物件。
b.在物件建立好後,執行getInstance()方法將不需要獲取鎖,直接返回建立好的物件。

問題:當代碼讀取到m_instance不為空,m_instance引用的物件有可能還沒有完成初始化。就會出出現問題。

m_instance = new Lazysingleton();
可以分解為:
memory=allocate();1.分配物件的記憶體空間
ctorInstance(memory);2.初始化物件
instance=memory;3.設定instance指向剛分配的記憶體地址

在Java記憶體模型中為了優化程式碼會重排程式碼,會導致執行緒看到一個還沒被初始化的物件。


執行緒安全的延遲初始化方案:

1、基於volatile的解決

宣告volatile,初始化程式碼重排就會被禁止,此方案是通過禁止程式碼重排來實現執行緒安全的延遲載入。
建立物件的過程,例項化物件一般分為三個過程。
    1、分配記憶體空間。
    2 、初始化物件。
    3 、將記憶體空間地址賦值給物件的引用。
  但是由於重排序的緣故,步驟2、3可能會發生重排序,其過程如下
    1、分配記憶體空間
    2、將記憶體空間的地址賦值給對應的引用
    3、初始化物件
 如果不加volatile的話,可能執行緒1在初始化的時候重排序了,執行緒2看到singleton != null,已經返回singleton,其實執行緒1還沒有完成初始化,僅僅只不過是分配了記憶體空間而已!


5、基於類初始化的解決方案(使用靜態內建類實現單例)

除了使用DCL解決多執行緒單例模式的非執行緒安全的問題,使用靜態內建類也可以實現同樣的效果。










每天努力一點,每天都在進步。