單例設計模式

單例設計模式

概念:單例對象的類必須保證只有⼀個實例存在。

適⽤場景: 單例模式只允許創建⼀個對象,因此節省記憶體,加快對象訪問速度,因此對象需要被公⽤的場合適合使⽤,如多個模組使⽤同⼀個數據源連接對象等等。如:

  1. 需要頻繁實例化然後銷毀的對象。
  2. 創建對象時耗時過多或者耗資源過多,但⼜經常⽤到的對象。
  3. 有狀態的⼯具類對象。
  4. 頻繁訪問資料庫或⽂件的對象。

常⻅寫法:

public class Singleton {
    /**
     * 優點:沒有執行緒安全問題,簡單
     * 缺點:提前初始化會延⻓類載入器載入類的時間;如果不使⽤會浪費記憶體空間;不能傳遞參數
     */
    private static final Singleton instance = new Singleton();
    private Singleton(){};
    public static Singleton getInstance(){
        return instance;
        }
}

餓漢式:在實例化類的時候就會創建對象。

優點:

  • 會提前初始化,你調用的時候就不需要再去初始化。性能是好的。
  • 沒有執行緒安全問題,簡單

缺點:

  • 提前初始化會延⻓類載入器載入類的時間;
  • 如果不使⽤會浪費記憶體空間,因為,即使不用它,它也會在那;
  • 不能傳遞參數。
public class Singleton{
    /**
    * 優點:解決執行緒安全,延遲初始化( Effective Java推薦寫法)
    */
    private Singleton(){}
    public static Singleton getInstance () {
        return Holder.SINGLE_TON;
        }
    private static class Holder{
        private static final Singleton SINGLE_TON = new Singleton();
    }
}

懶漢式:延遲初始化的手段

調用它的時候,會去初始化;不調用它的時候,就不初始化。

優點:

解決執行緒安全,延遲初始化( Effective Java推薦寫法)

 public class Singleton {
     private volatile static Singleton uniqueSingleton;
     private Singleton() {
     }
     
     public Singleton getInstance() {
         if (null == uniqueSingleton) {//第一重檢查
             synchronized (Singleton.class) {//第一重加鎖
                 if (null == uniqueSingleton) {//第二重檢查
                     uniqueSingleton = new Singleton();//創建對象
                 }
             }
         }
         return uniqueSingleton;
     }
 }        

雙重檢查鎖原理:

第一次判斷為空以後,第一次加鎖。

因為加鎖不是一個原子操作,在加鎖的過程中,uniqueSingleton對象可能已經被初始化掉了,所以加鎖完畢以後,還需要去判斷一下,uniqueSingleton對象是不是為空?

加鎖後,進行第二次檢驗(檢查)

如果uniqueSingleton對象為空,進行創建對象。

注意:

volatile

  • 會保證可見性
  • 不會保證原子性。

具體原理:

加了一個log的前置指令(即記憶體屏障),會保證屏障後面的指令不會放到屏障之前,也不會把屏障之前的指令放到屏障之後。在執行到屏障的時候,一定會保證屏障之前的指令全執行完了。

進行寫操作的時候:

  • 有快取:寫操作會導致快取失效;
  • 無快取:將快取里的修改,直接寫入到快取里。

volatile的作用:

  1. 阻止指令重排序;

    加入volatile,會加入一個記憶體屏障。加入記憶體屏障後順序就不能變了。是禁止指令重排序。

  1. 保證可見性;

    平時操作時,會有一個主記憶體和一個工作記憶體的。

    如果不加volatile,數據是放在工作記憶體的,進行操作的時候,需要從工作記憶體刷到主記憶體。加了volatile,相當於不用工作記憶體,直接用主記憶體。相當於多個執行緒對一個對象操作的時候,多個執行緒對結果是可見的。

為什麼考慮指令重排序?

volatile指令重排序

在執⾏程式時,為了提供性能,處理器和編譯器常常會對指令進⾏重排序,但是不能隨意重排序,不是你 想怎麼排序就怎麼排序,它需要滿⾜以下兩個條件:

  1. 在單執行緒環境下不能改變程式運⾏的結果;
  2. 存在數據依賴關係的不允許重排序

指令排序

uniqueSingleton = new Singleton();
  1. 分配記憶體空間
  2. 初始化對象
  3. 將對象指向剛分配的記憶體空間

但是有些編譯器為了性能的原因,可能會將第⼆步和第三步進⾏重排序,順序就成了:

  1. 分配記憶體空間
  2. 將對象指向剛分配的記憶體空間
  3. 初始化對象

現在考慮重排序後,兩個執行緒發⽣了以下調⽤:

Time Thread A Thread B
T1 檢查到uniqueSingleton為空
T2 獲取鎖
T3 再次檢查到uniqueSingleton為 空
T4 為uniqueSingleton分配記憶體空 間
T5 將uniqueSingleton指向記憶體空 間
T6 檢查到uniqueSingleton不為空
T7 訪問uniqueSingleton(此時對 象還未完成初始化)
T8 初始化uniqueSingleton

在這種情況下,T7時刻執行緒B對uniqueSingleton的訪問,訪問的是⼀個初始化未完成的對象。

使⽤了volatile關鍵字後,重排序被禁⽌,所有的寫(write)操作都將發⽣在讀(read)操作之前。

單例模式的破壞

Singleton sc1 = Singleton.getInstance(); 
Singleton sc2 = Singleton.getInstance(); 
System.out.println(sc1); // sc1,sc2是同⼀個對象 
System.out.println(sc2); 

/*通過反射的⽅式直接調⽤私有構造器*/ 
Class clazz = (Class) Class.forName("com.le arn.example.Singleton"); 
Constructor c = clazz.getDeclaredConstructor(null); 
c.setAccessible(true); // 跳過許可權檢查 
Singleton sc3 = c.newInstance(); 
Singleton sc4 = c.newInstance(); 
System.out.println("通過反射的⽅式獲取的對象sc3:" + sc3); // sc3,sc 4不是同⼀個對象  
System.out.println("通過反射的⽅式獲取的對象sc4:" + sc4); 
//以上程式碼是說明單例是可以被破壞的

//防⽌反射獲取多個對象的漏洞 (即防止單例被破壞的解決辦法。)
private Singleton() { 
    if (null != SingletonClassInstance.instance) 
        throw new RuntimeException(); 
}

spring中bean的單例

a. 當多個⽤戶同時請求⼀個服務時,容器會給每⼀個請求分配⼀個執行緒,這時多個執行緒會並發執⾏該請求對應的業務邏輯(成員⽅法)。此時就要注意了,如果該處理邏輯中有對單例狀態的修改(體現為該單例的成員屬性),則必須考慮執行緒同步問題。

有狀態就是有數據存儲功能有狀態對象(Stateful Bean)就是有實例變數的對象,可以保存數據,是⾮執行緒安全的。在不同⽅法調⽤間不保留任何狀態。

⽆狀態就是⼀次操作,不能保存數據。⽆狀態對象(Stateless Bean),就是沒有實例變數的對象 . 不能保存數據,是不變類,是執行緒安全的。

b. 實現

public abstract class AbstractBeanFactory implements Configurable
    BeanFactory{
    /**
    * 充當了Bean實例的快取,實現⽅式和單例註冊表相同
    */
    private final Map singletonCache=new HashMap();
    public Object getBean(String name)throws BeansException{
        return getBean(name,null,null);
    }
    ...
        public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{
        //對傳⼊的Bean name稍做處理,防⽌傳⼊的Bean name名有⾮法字元(或則做轉碼)
        String beanName=transformedBeanName(name);
        Object bean=null;
        //⼿⼯檢測單例註冊表
        Object sharedInstance=null;
        //使⽤了程式碼鎖定同步塊,原理和同步⽅法相似,但是這種寫法效率更⾼
        synchronized(this.singletonCache){
            sharedInstance=this.singletonCache.get(beanName);
        }
        if(sharedInstance!=null){
            ...
                //返回合適的快取Bean實例
                bean=getObjectForSharedInstance(name,sharedInstance);
        }else{...
            //取得Bean的定義
            RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);
              ...
                  //根據Bean定義判斷,此判斷依據通常來⾃於組件配置⽂件的單例屬性開關
                  //<bean id="date" class="java.util.Date" scope="singleton"/>
                  //如果是單例,做如下處理
                  if(mergedBeanDefinition.isSingleton()){
                      synchronized(this.singletonCache){//再次檢測單例註冊表
                          sharedInstance=this.singletonCache.get(beanName);
                          if(sharedInstance==null){
                              ...
                                  try {
                                      //真正創建Bean實例 
                                      sharedInstance=createBean(beanName,mergedBeanDefinition,args);
                                      //向單例註冊表註冊Bean實例
                                      addSingleton(beanName,sharedInstance);
                                  }catch (Exception ex) {
                                      ...
                                  }finally{
                                      ...
                                  }
                          }
                      }
                      bean=getObjectForSharedInstance(name,sharedInstance);
                  }
              //如果是⾮單例,即prototpye,每次都要新創建⼀個Bean實例
              //<bean id="date" class="java.util.Date" scope="prototype"/>
              else{
                  bean=createBean(beanName,mergedBeanDefinition,args);
              }
             }
        ...
            return bean;
    }
}