單例模式的雙重檢測


單例模式是設計模式中比較常見簡單的一種,典型雙重檢測寫法如下:

public class SingletonClass { private volatile static SingletonClass instance = null; public static SingletonClass getInstance() { if (instance == null) { synchronized (SingletonClass.class) { if(instance == null) { instance = new SingletonClass(); } } } return instance; } private SingletonClass() { } }

接下來對該寫法進行分析,為何這樣寫?

一、為何要同步:

多線程情況下,若是A線程調用getInstance,發現instance為null,那么它會開始創建實例,如果此時CPU發生時間片切換,線程B開始執行,調用getInstance,發現instance也null(因為A並沒有創建對象),然后B創建對象,然后切換到A,A因為已經檢測過了,不會再檢測了,A也會去創建對象,兩個對象,單例失敗。因此要同步。

 

二、同步為何不用 public synchronized static SingletonClass getInstance(),也就是說為何不同步這個方法,而要同步下面的語句:

因為synchronized修飾的同步塊可是要比一般的代碼段慢上幾倍,如果經常調用getInstance,那么性能問題就得考慮了。

 

三、最外層為何要有if (instance == null)判斷:

因為我們在分析二中,發現依舊存在着性能問題,也就是說,只要getInstance方法被調用,那么就會執行同步這個操作,於是我們加個判斷,當instance沒有被實例化的時候,也就是需要去實例化的時候才去同步。

 

四、instance為何要有volatile 修飾:

這個問題就涉及到了編譯原理,所謂編譯,就是把源代碼“翻譯”成目標代碼——大多數是指機器代碼——的過程。針對Java,它的目標代碼不是本地機器代碼,而是虛擬機代碼。編譯原理里面有一個很重要的內容是編譯器優化。所謂編譯器優化是指,在不改變原來語義的情況下,通過調整語句順序,來讓程序運行的更快。這個過程成為reorder。

JVM實現可以自由的進行編譯器優化。而我們創建變量的步驟:

1、申請一塊內存,調用構造方法進行初始化。

2、分配一個指針指向這塊內存。

而這兩個操作,JVM並沒有規定誰在前誰在后,那么就存在這種情況:線程A開始創建SingletonClass的實例,此時線程B調用了getInstance()方法,首先判斷instance是否為null。按照我們上面所說的內存模型,A已經把instance指向了那塊內存,只是還沒有調用構造方法,因此B檢測到instance不為null,於是直接把instance返回了——問題出現了,盡管instance不為null,但它並沒有構造完成,就像一套房子已經給了你鑰匙,但你並不能住進去,因為里面還沒有收拾。此時,如果B在A將instance構造完成之前就是用了這個實例,程序就會出現錯誤了。

在JDK 5之后,Java使用了新的內存模型。volatile關鍵字有了明確的語義——在JDK1.5之前,volatile是個關鍵字,但是並沒有明確的規定其用途——被volatile修飾的寫變量不能和之前的讀寫代碼調整,讀變量不能和之后的讀寫代碼調整!因此,只要我們簡單的把instance加上volatile關鍵字就可以了。

 

轉載請標明出處https://www.cnblogs.com/tangZH/p/10031337.html 

參考鏈接http://blog.51cto.com/devbean/203501

更多查看:http://77blogs.com/?p=488


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM