對項目代碼進行掃描時,出現靜態掃描嚴重問題,發現是因爲多線程環境下沒有正確建立單例所致使。html
本項目使用的
JDK 1.7+
。java
項目代碼以下(修改了類名,但核心沒變)多線程
static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { synchronized (mutexObj) { if (tmp == null) { tmp = new Singleton(); cache = tmp; } } } return tmp; } }
按照項目生成單例代碼,使用以下測試類進行測試併發
public class Test { public static void main(String[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString()); } }); thread.setName("Thread" + i); thread.start(); } } static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { synchronized (mutexObj) { if (tmp == null) { tmp = new Singleton(); cache = tmp; } } } return tmp; } } }
輸出結果以下:測試
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4線程
從結果看,都生成了同一個實例,彷佛不存在問題,多線程環境下確實不太好重現問題,現改動代碼以下:3d
static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in outer if block"); synchronized (mutexObj) { System.out.println(Thread.currentThread().getName() + " in synchronized block"); if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in inner if block"); tmp = new Singleton(); cache = tmp; } System.out.println(Thread.currentThread().getName() + " out inner if block"); } System.out.println(Thread.currentThread().getName() + " out synchronized block"); } System.out.println(Thread.currentThread().getName() + " out outer if block"); return cache; } }
上述代碼中添加了Thread.sleep(1)
這條語句,其中,Thread.sleep(1)
進行休眠時,線程不會釋放擁有的鎖,而且打印了相關的語句,便於查看線程正運行在哪裏的狀態。code
再次測試,輸出結果以下:htm
Thread2 in outer if block
Thread1 in outer if block
Thread0 in outer if block
Thread2 in synchronized block
Thread2 in inner if block
Thread2 out inner if block
Thread2 out synchronized block
Thread0 in synchronized block
Thread2 out outer if block
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@60b07af1
Thread0 in inner if block
Thread0 out inner if block
Thread0 out synchronized block
Thread1 in synchronized block
Thread0 out outer if block
Thread1 in inner if block
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@625795ce
Thread1 out inner if block
Thread1 out synchronized block
Thread1 out outer if block
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@642c39d2blog
從結果看,生成了3個不一樣的實例,而且每一個線程都執行了完整的流程,而且可知單例的建立存在問題。在分析緣由前簡單瞭解下多線程模型,多線程模型以下:
每一個線程有本身獨立的工做空間,線程間進行通訊是經過主內存完成的,想了解詳細內容可參見以下連接:內存模型或深刻理解java內存模型。
知道每一個線程會有一份tmp拷貝後,配合打印輸出,就不難分析出緣由。
按照《Effective Java》一書中建立單例的推薦,可以使用以下兩種解決方法
須要配合
volatile
關鍵字使用,而且須要JDK
版本在1.5
以上,核心代碼以下
static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { tmp = cache; synchronized (mutexObj) { if (tmp == null) { tmp = new Singleton(); cache = tmp; } } } return tmp; } }
進行以下測試(添加打印語句方便分析):
public class Test { public static void main(String[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString()); } }); thread.setName("Thread" + i); thread.start(); } } static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in outer if block"); synchronized (mutexObj) { System.out.println(Thread.currentThread().getName() + " in synchronized block"); tmp = cache; if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in inner if block"); tmp = new Singleton(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } cache = tmp; } System.out.println(Thread.currentThread().getName() + " out inner if block"); } System.out.println(Thread.currentThread().getName() + " out synchronized block"); } System.out.println(Thread.currentThread().getName() + " out outer if block"); return tmp; } } }
輸出結果以下:
Thread0 in outer if block
Thread0 in synchronized block
Thread0 in inner if block
Thread2 in outer if block
Thread1 in outer if block
Thread0 out inner if block
Thread0 out synchronized block
Thread1 in synchronized block
Thread1 out inner if block
Thread1 out synchronized block
Thread1 out outer if block
Thread0 out outer if block
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f
Thread2 in synchronized block
Thread2 out inner if block
Thread2 out synchronized block
Thread2 out outer if block
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f
從結果中和線程運行步驟能夠看到三個線程併發的狀況下,只生成了惟一實例。
無
JDK
版本限制,也不須要使用volatile
關鍵字便可完成單例模式,核心代碼以下:
static class Singleton { private Singleton() { } private static class InstanceHolder { public static Singleton instance = new Singleton(); } public static Singleton getInstance() { return InstanceHolder.instance; } }
進行以下測試:
public class Test { public static void main(String[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString()); } }); thread.setName("Thread" + i); thread.start(); } } static class Singleton { private Singleton() { } private static class InstanceHolder { public static Singleton instance = new Singleton(); } public static Singleton getInstance() { return InstanceHolder.instance; } } }
運行結果以下:
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7
該模式可保證使用時纔會初始化變量,達到延遲初始化目的。
單例模式在多線程環境下不太好編寫,而且不容易重現異常,編寫時須要謹慎,在項目中遇到問題也須要多總結和記錄。