1. 程式人生 > >JAVA單例(懶漢模式)執行緒安全

JAVA單例(懶漢模式)執行緒安全

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.