咱們常常看到的單例模式,按加載時機能夠分爲:餓漢方式和懶漢方式;按實現的方式,有:synchronized修飾方法、雙重檢查加鎖,內部類方式和枚舉方式等等。另外還有一種經過Map容器來管理單例的方式。
html
今天寫了一個工具類,以單例的形式持有內部具體處理類的引用。java
public class LogProcessorUtils { private static LogProcessorInterface logProcessor = null; private LogProcessorUtils() { } public static String processLog2Json(String s) { // 保證logProcessor單例 if (logProcessor == null) { synchronized (LogProcessorUtils.class) { if (logProcessor == null) { logProcessor = new LogProcessorImpl(); } } } return logProcessor.process(s); } }
可是,在FindBugs插件中爆出bug。如下是網頁中的介紹:web
- DC: Possible double check of field (DC_DOUBLECHECK)
This method may contain an instance of double-checked locking. This idiom is not correct according to the semantics of the Java memory model. For more information, see the web page http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html.函數
大體意思是,以雙重檢查鎖定實現的單例,在理論是正確的,可是實際上,由於Java內存模型的緣由,可能形成錯誤,不推薦使用。工具
網上查閱了資料,參考《http://blog.csdn.net/chenchaofuck1/article/details/51702129》.net
解釋以下:插件
public static Singleton getInstance(){ if (instance == null){ synchronized(Singleton.class) { //1 if (instance == null) //2 instance = new Singleton(); //3 } } return instance; }
雙重檢查鎖定背後的理論是:在 //2 處的第二次檢查使(如清單 3 中那樣)建立兩個不一樣的 Singleton 對象成爲不可能。假設有下列事件序列:
線程 1 進入 getInstance() 方法。線程
雙重檢查鎖定背後的理論是完美的。不幸地是,現實徹底不一樣。雙重檢查鎖定的問題是:並不能保證它會在單處理器或多處理器計算機上順利運行。
雙重檢查鎖定失敗的問題並不歸咎於 JVM 中的實現 bug,而是歸咎於 Java 平臺內存模型。內存模型容許所謂的「無序寫入」,這也是這些習語失敗的一個主要緣由。code
爲解釋該問題,須要從新考察上述清單 4 中的 //3 行。此行代碼建立了一個 Singleton 對象並初始化變量 instance 來引用此對象。這行代碼的問題是:在 Singleton 構造函數體執行以前,變量 instance 可能成爲非 null 的。orm
什麼?這一說法可能讓您始料未及,但事實確實如此。在解釋這個現象如何發生前,請先暫時接受這一事實,咱們先來考察一下雙重檢查鎖定是如何被破壞的。假設清單 4 中代碼執行如下事件序列:
- 線程 1 進入 getInstance() 方法。
爲展現此事件的發生狀況,假設爲代碼行 instance =new Singleton(); 執行了下列僞代碼: instance =new Singleton();
mem = allocate(); //Allocate memory for Singleton object. instance = mem; //Note that instance is now non-null, but //has not been initialized. ctorSingleton(instance); //Invoke constructor for Singleton passing //instance.
這段僞代碼不只是可能的,並且是一些 JIT 編譯器上真實發生的。執行的順序是顛倒的,但鑑於當前的內存模型,這也是容許發生的。JIT 編譯器的這一行爲使雙重檢查鎖定的問題只不過是一次學術實踐而已。
爲說明這一狀況,假設有清單 5 中的代碼。它包含一個剝離版的 getInstance() 方法。我已經刪除了「雙重檢查性」以簡化咱們對生成的彙編代碼(清單 6)的回顧。咱們只關心 JIT 編譯器如何編譯 instance=new Singleton(); 代碼。此外,我提供了一個簡單的構造函數來明確說明彙編代碼中該構造函數的運行狀況。
public class LogProcessorUtils { private LogProcessorUtils() { } /** * 保證logProcessor單例 */ private static class ProcessorSingletonHolder { private final static LogProcessorInterface logProcessor = new LogProcessorImpl(); } public static String processLog2Json(String s) { return ProcessorSingletonHolder.logProcessor.process(s); } }
以後,FindBugs警告消失。