Java基礎-單例模式

Merbng發表於2018-05-09

概述

使用一個私有建構函式、一個私有靜態變數以及一個公有靜態函式來實現。 私有建構函式保證了不能通過建構函式來建立物件例項,只能通過公有靜態函式返回唯一的私有靜態變數。

實現

  • 懶漢式-執行緒不安全

以下實現中,私有靜態變數uniqueInstance被延遲例項化,這樣做的好處是,如果沒有用到該類,那麼就不會例項化uniqueInstance,從而節約資源。

  public class SingLeton{
  private static SingLeton uniqueInstance;

  private SingLeton(){
  }

  public static SingLeton getUniqueInstance(){
    if(uniqueInstance==null){
    uniqueInstance =new SingLeton();
    }
    return uniqueInstance;
  }
}
複製程式碼
  • 懶漢式-執行緒安全

只需要對getUniqueInstance()方法加鎖,那麼在一個時間點只能有一個執行緒能夠進入該方法,從而避免了uniqueInstance進行多次例項化的問題。 但是這樣有一個問題,就是當一個執行緒進入該方法後,其他執行緒檢視進入該方法都必須等待,因此效能上有一定的損耗。

public static synchroinized SingLeton getUniqueInstance(){
    if(uniqueInstance ==null){
      uniqueInstance =new SingLeton();
      }
    return uniqueInstance;
}
複製程式碼
  • 餓漢式-執行緒安全

執行緒不安全問題主要由uniqueInstance被例項化了多次,如果uniqueInstance採用直接例項化的話,就不會被例項化多次,也就不會產生執行緒不安全的問題,但是直接例項化的方式也丟失了延遲例項化帶來的節約資源的優勢。

private static SingLeton uniqueInstance = new SingLeton();
複製程式碼
  • 雙重校驗鎖-執行緒安全

uniqueInstance只需要被例項化一次,之後就可以直接使用了,加鎖操作只需要對例項化那部分程式碼進行,也就是說,只有當uniqueInstance沒有被例項化時,才需要進行加鎖。 雙重校驗鎖先判斷uniqueInstance是否已經被例項化了,如果沒有被例項化,那麼才對例項化語句進行加鎖。

public class SingLeton{
   private volatile static SingLeton uniqueInstance;
   private SinaLeton uniqueInstance;

  public static SingLeton getUniqueInstance(){
    if(uniqueInstance==null){
      synchronized(SingLeton.class){
        if(uniqueInstance == null){ 
            uniqueInstance = new SingLeton();
        }
      }
    }
    return uniqueInstance;
  }
}
複製程式碼

考慮下面的實現,也就是隻使用了一個if語句,在uniqueInstance==null的情況下,如果兩個執行緒同時執行if語句,那麼兩個執行緒就會同時進入if語句塊內,雖然在if語句塊內有加鎖操作,但是兩個執行緒都會執行uniqueInstance=new SingLeton(),這條語句,只是早晚的問題,也就是說會進行兩次例項化,從而產生了兩個例項,因此必須使用雙重校驗鎖,也就是需要使用兩個if判斷。

if(uniqueInstance == null){
  synchronized(SingLeton.class){
   uniqueInstance =new SingLeton();
  }
}
複製程式碼

uniqueInstance 採用volatile關鍵字修飾也是很也必要的。 uniqueInstance=new SingLeton();這段程式碼其實是分為三步執行。

  1. 分配記憶體空間。
  2. 初始化物件。
  3. 將uniqueInstance指向分配的記憶體地址。

但是由於JVM具有指令重排的特性,有可能執行順序變為了1>3>2,這在單執行緒情況下自然是沒有問題的,但是如果是多執行緒就有可能B執行緒獲得是一個還沒有初始化的物件以致於程式出錯。 使用使用volatile修飾,目的是禁止JVM的指令重排,保證在多執行緒環境下也能正常執行。

相關文章