天天看點

深入分析Java單例模式的各種方案單例模式

深入分析Java單例模式的各種方案單例模式

所有單例模式都有一個共性,那就是這個類沒有自己的狀态。也就是說無論這個類有多少個執行個體,都是一樣的;然後除此者外更重要的是,這個類如果有兩個或兩個以上的執行個體的話程式會産生錯誤。

出于性能考慮,采用<code>雙重檢查加鎖的模式</code>

<code>雙重檢查加鎖模式</code>相對于普通的單例和加鎖模式而言,從性能和線程安全上來說都有很大的提升和保障。然而<code>雙重檢查加鎖模式</code>也存在一些隐蔽不易被發現的問題。首先我們要明白在JVM建立新的對象時,主要要經過三個步驟。

配置設定記憶體

初始化構造器

将對象指向配置設定的記憶體位址

這樣的順序在雙重加鎖模式下是麼有問題的,對象在初始化完成之後再把記憶體位址指向對象。

但是現代的JVM為了追求執行效率會針對位元組碼(<code>編譯器級别</code>)以及指令和記憶體系統重排序(<code>處理器重排序</code>)進行調優,這樣的話就<code>有可能</code>(<code>注意是有可能</code>)導緻2和3的順序是相反的,一旦出現這樣的情況問題就來了。

java源代碼到最終實際執行的指令序列:

深入分析Java單例模式的各種方案單例模式

前面的雙重檢查鎖定示例代碼的(instance = new Singleton();)建立一個對象。這一行代碼可以分解為如下的三行僞代碼:

上面三行僞代碼中的2和3之間,可能會被重排序(在一些JIT編譯器上,這種重排序是真實發生的,詳情見參考文獻1的“Out-of-order writes”部分)。2和3之間重排序之後的執行時序如下:

多線程并發執行的時候的情況:

先來說說<code>Volatile</code>這個關鍵字的含義:

可以很好地解決可見性問題 但不能確定原子性問題(通過 <code>synchronized</code> 進行解決) 禁止指令的重排序(單例主要用到此JVM規範)

<code>Volatile 雙重檢查加鎖模式</code>

利用靜态内部類的方式來建立,因為靜态屬性由JVM確定第一次初始化時建立,是以也不用擔心并發的問題出現。當初始化進行到一半的時候,别的線程是無法使用的,因為JVM會幫我們強行同步這個過程。另外由于靜态變量隻初始化一次,是以singleton仍然是單例的。

深入分析Java單例模式的各種方案單例模式

這個方案的實質是:允許“問題的根源”的三行僞代碼中的2和3重排序,但不允許非構造線程(這裡指線程B)“看到”這個重排序。

<code>靜态内部類</code>的方式

然而,雖然<code>靜态内部類</code>模式可以很好地避免并發建立出多個執行個體的問題,但這種方式仍然有其存在的隐患。

一旦一個執行個體被持久化後重新生成的執行個體仍然有可能是不唯一的。

由于java提供了反射機制,通過反射機制仍然有可能生成多個執行個體。

問題點及解決辦法

ObjectInputStream中的<code>readOrdinaryObject</code>。

調用自定義的<code>readResolve</code>方法

<code>解決方案</code> : 私有構造方法中進行添加标志判斷。

<code>枚舉實作單例的優勢</code>

自由序列化; 保證隻有一個執行個體(即使使用反射機制也無法多次執行個體化一個枚舉量); 線程安全;

通過<code>ThreadLocal</code>的方式

參考文檔:

<a href="http://www.ibm.com/developerworks/java/library/j-dcl/index.html">http://www.ibm.com/developerworks/java/library/j-dcl/index.html</a> 《Java 并發變成的藝術》 《Effective Java中文版第2版》 <a href="http://www.cnblogs.com/345214483-qq/p/6472158.html">http://www.cnblogs.com/345214483-qq/p/6472158.html</a> <a href="http://www.cnblogs.com/lthIU/p/6240128.html">http://www.cnblogs.com/lthIU/p/6240128.html</a> <a href="http://www.uml.org.cn/sjms/201110184.asp">http://www.uml.org.cn/sjms/201110184.asp</a>