最近在公司寫需求時遇到了多線程與單例一同出現的情況。
這個時候想到的就是線程安全以及單例的定義了,雖然單例指的是在內存中它只有一份,但是並不是說就是線程安全的。
所以,我當時就到網上找了關於多線程下單例的線程安全問題的資料,然后就知道如下博客:高並發下線程安全的單例模式(最全最經典)
其中,博主最推薦的寫作方式如下:
為了達到線程安全,又能提高代碼執行效率,這里可以采用DCL(Double Check Locking)的雙檢查鎖機制來完成
public class MySingleton { //使用volatile關鍵字保其可見性 volatile private static MySingleton instance = null; private MySingleton(){} public static MySingleton getInstance() { try { if(instance != null){//懶漢式 }else{ //創建實例之前可能會有一些准備性的耗時工作 Thread.sleep(300); synchronized (MySingleton.class) { if(instance == null){//二次檢查 instance = new MySingleton(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return instance; } }
看了看內容確實是這個道理,然后就把這段代碼拿來使用了。然后在實際測試中發現,其並沒有保證線程安全的問題。
之后在同事的指點下發現,其實上文一段的線程安全僅僅只是在未實例化單例的前提下,以線程安全的方式實例化單例,使之在高並發多線程的環境下有且僅被new過一次。
也就是說,在單例被實例化之后,這段代碼是並沒有什么作用的。
單例被實例化之后,instance != null一直成立,使getInstance()每次都是return instance。所以,多線程都能拿到指向同一個實例的引用。
所以即使是使用了這種雙檢查鎖機制的代碼,依然要對后面要使用到的公用方法做同步,以免出現問題。
而對公用方法做同步的操作也分兩種情況。一種是公用方法里只有局部變量,那么此時不做同步也是可以的,因為局部變量只會存在於相應的線程內存里,並不會被其它線程所影響。另外一種是含有成員變量,如果成員變量只有讀的操作,那不同步也可以;如果成員變量涉及讀寫操作,那么就要對相應的方法進行同步了。
局部變量不會受多線程影響
成員變量會受到多線程影響
多個線程應該是調用的同一個對象的同一個方法:
如果方法里無成員變量,那么不受任何影響
如果方法里有成員變量,只有讀操作,不受影響
存在寫操作,考慮多線程影響值
