單例模式是JAVA設計模式中最常用、最重要的設計模式之一。
最簡單的寫法是:
public class TestSingleton { private static String ourInstance = null; //私有化構造器 private TestSingleton() { } //提供靜態方法給外部訪問 public static String getOurInstance(){ if(ourInstance == null){ ourInstance="單例模式賦值成功!!!"; System.out.print(ourInstance); } return ourInstance; } }
這種寫法的單例模式是線程不安全的,下面用代碼來模擬一下多線程並發,代碼的執行情況:
public class Test { /** * 線程池 */ final static ExecutorService threadPool = Executors.newCachedThreadPool(); /** * 並發數 */ private final static int CONCURRENT_COUNT = 10; /** *信號量 */ final static CountDownLatch locks = new CountDownLatch(CONCURRENT_COUNT); public static void main(String[] args) { for (int i = 0; i < CONCURRENT_COUNT; i++) { threadPool.execute(new Runnable() { @Override public void run() { locks.countDown(); TestSinleton.getOurInstance(); } } ); }
locks.await(); } }
代碼非常簡單,用線程池執行CONCURRENT_COUNT 個任務,每個任務執行到
locks.countDown()
時被阻塞,當被阻塞的任務數達到CONCURRENT_COUNT個時,所有任務同時往下執行,這樣模擬了多個用戶並發的場景。(CountDownLatch 的使用方法)
思考:如果以上寫法的單例模式線程安全,那麼控制台只會打印一次:單例模式賦值成功!!!
運行3次程序,查看控制台輸出:
"C:\Program Files\Java\jdk1.8.0_101\bin\java" ... 單例模式復制成功!!!單例模式復制成功!!! Process finished with exit code 0
"C:\Program Files\Java\jdk1.8.0_101\bin\java" ... 單例模式賦值成功!!!單例模式賦值成功!!!單例模式賦值成功!!! Process finished with exit code 1
"C:\Program Files\Java\jdk1.8.0_101\bin\java"... 單例模式賦值成功!!! Process finished with exit code 1
發現並不是想的這樣,很容易就出現了變量被多次賦值的情況。所以,以上的單例模式是線程不安全的。
思考為什麼會出現這種情況?
因為多用戶並發操作的情況下,if(ourInstance == null) 可能被同時執行,如果ourInstance為空的話,就會出現多個線程判斷(ourInstance == null)為true了。
修改代碼:
public class TestSinleton { private static String ourInstance = null; //私有化構造器 private TestSinleton() { } //提供靜態方法給外部訪問 public static String getOurInstance(){ if(ourInstance == null){ synchronized (TestSinleton.class){ if(ourInstance == null){ ourInstance="單例模式復制成功!!!"; System.out.print(ourInstance); } } //ourInstance="單例模式賦值成功!!!"; //System.out.print(ourInstance); } return ourInstance; } }
第一次判空,大部分線程被擋掉。繼續往下是一個synchronized 方法,只允許單線程執行,所以第二次判空就不會存在多個線程同時執行的情況。
多次運行程序,查看控制台:
"C:\Program Files\Java\jdk1.8.0_101\bin\java" ... 單例模式復制成功!!! Process finished with exit code 0
發現只會打印一次了。