JAVA單例(懶漢模式)執行緒安全
阿新 • • 發佈:2018-12-31
JAVA中單例模式分為兩種
1、餓漢模式
2、懶漢模式
餓漢模式不存線上程安全問題; 而懶漢模式存線上程安全問題。詳見下文,來自網路:
單例的多執行緒執行緒安全問題的描述
通常的多執行緒的執行緒安全問題,往往被描述成"多執行緒共享執行緒例項變數"
但多執行緒下的例項變數如果是單例的話,本來就是該共享的,因為單例在同一JVM下只有一個
所以平常的執行緒安全問題,在這裡正好相反,如果多執行緒不共享單例的例項變數,才是真正的執行緒安全問題
這也證明了執行緒安全的本質是"實際值和理論值不符",而不能簡單的描述為"多執行緒共享執行緒例項變數就是執行緒安全問題"
自然,如果多執行緒共享單例,自然也共享單例的狀態
下面這樣的程式碼,採用"懶漢"形式的初始化模式,在多執行緒下,可能會發生錯誤
如果同時兩個執行緒訪問這個單例,一個發現為空,開始建立物件。但是同時在沒有建立完的時候,又一個執行緒進來了,因為上一個執行緒沒有建立完例項,所以第二個例項仍能判斷為空。又建立了一個。就不是單例了,單例失敗
單例類 public class MySingleton2 { private static MySingleton2 instance; private MySingleton2(){} public static MySingleton2 getInstance() throws InterruptedException { if(instance==null){ Thread.sleep(10000) instance = new MySingleton2(); } return instance; } |
執行緒類 呼叫單例類 public void run(){ MySingleton2 mySingleton2=null; try { mySingleton2 = MySingleton2.getInstance(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(mySingleton2+":"+mySingleton2.hashCode()); } |
測試程式碼:,起10個執行緒,都引用同一例項(單例項多執行緒) MyThread myThread= new MyThread(); for(int i=0;i<10;i++){ Thread t = new Thread(myThread,String.valueOf(i)); t.start(); } |
執行結果: 看出現多個不同的單例類的例項,違反了單例的規範. [email protected]:19336051 [email protected]:6336176 [email protected]:23899971 [email protected]:6718604 [email protected]:8918002 [email protected]:30771886 [email protected]:8637543 [email protected]:14718739 [email protected]:14577460 [email protected]:22474382 |
所以這種"懶漢初始化"只能在單執行緒下使用。
"懶漢初始化"解決:
- 法1.用同步鎖保護,但會造成效能低下。而且存在"二次檢查"的問題.
採用同步getInstance()方法
public synchronized static MySingleton3 getInstance() throws InterruptedException { if(instance==null){ Thread.sleep(10000); instance = new MySingleton3(); } return instance; } |
執行:問題解決, [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 |
同步程式碼塊
同步方法通常開銷很大,造成效能低下,因此建議採用同步程式碼塊,但同步程式碼塊存在"二次檢查"的問題
下面對初始化程式碼塊加了同步
public static MySingleton3 getInstance() throws InterruptedException { if(instance==null){ synchronized(MySingleton3.class){ Thread.sleep(10000); instance = new MySingleton3(); } } |
執行: 仍出現多個例項 [email protected]:19336051 [email protected]:6336176 [email protected]:23899971 [email protected]:6718604 [email protected]:8918002 [email protected]:30771886 [email protected]:8637543 [email protected]:14718739 [email protected]:14577460 [email protected]:22474382 |
加入二次檢查
public static MySingleton3 getInstance() throws InterruptedException { if(instance==null){ synchronized(MySingleton3.class){ if(instance==null){ Thread.sleep(10000); instance = new MySingleton3(); } } } return instance; } |
執行: 終於正確了 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 [email protected]:19336051 |
- 法2.採用"餓漢初始化"
private static Singleton instance = new Singleton(); |
或者採用靜態塊
private static final MySingleton1 instance; static { instance = new MySingleton1(); } |
餓漢初始化根本不會存線上程安全問題,但開銷是個問題,因為懶漢模式的使用理念是"用時才初始化",餓漢模式則是"不管用不用都初始化",這本身是和業界流行的資源使用習慣相違背的.
不過因為原理相對簡單,而且初始化開銷畢竟總是一次性的,這個開銷也許值得犧牲.所以很多人還是選擇相對簡單的法2.