1. 程式人生 > >Java設計模式 —— 單例模式

Java設計模式 —— 單例模式

單例模式的定義:

一個類只能建立一個例項,每個例項有一個全域性唯一的訪問點

我們為什麼需要單例模式:
  有一些物件我們只需要一個,如執行緒池(threadpool)、快取(cache)、日誌物件、充當驅動程式的物件等。如果創建出多個例項,就會導致很多問題的產生,例如:程式的行為異常,資源的使用過量
  spring 預設建立的物件是單例的,避免了頻繁建立物件,管理物件。提高了效能,同時也減少了記憶體和空間的開銷
  
為什麼不用靜態方法去代替單例模式:

單例設計模式體現的是面向物件,而通過靜態類去呼叫靜態方法體現的是基於物件。面向物件有很多優良特性。

比如單例設計模式的程式碼可以被複用(通過繼承,而靜態方法屬於類不可被繼承)
  靜態類被載入時即被初始化,長期駐留在記憶體中。單例模式可以被延遲初始化,使用時才被建立。

如何編寫單例模式程式碼:
  1.將建構函式私有化
  2.在類的內部建立例項
  3.提供獲取唯一例項的方法
單例模式的程式碼實現:

1 餓漢式:

/**
 * Description:單例模式——餓漢式.
 *
 * @author muronglan
 * @version 1.0
 * @date 2018/11/27 10:24
 */
public class Singleton {
  /** 1.私有化建構函式 */
private Singleton() {} /** 2.在類的內部建立例項 */ private static Singleton singleton = new Singleton(); /** 3.提供全域性唯一的訪問點 */ public Singleton instance(){ return singleton; } }

總結:
  在類載入的時候,就建立物件,靜態成員變數位於方法區中,執行緒間共享,所以每個執行緒都能訪問到該物件,該物件在其生命週期內全域性唯一。缺點就是如果該例項沒有被使用,則會造成記憶體浪費。
  
2 雙檢鎖的懶漢式:
  懶漢式,即用到的時候再建立物件,但是不加鎖的懶漢式,併發條件下會產生同步問題,為了避免這種問題,需要採用雙重檢查鎖的實現方式。
  
關鍵字:Volatile
Volatile有兩層語義:
  1 保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,新值對其他執行緒來說是立即可見的。(Volatile可以保證新的值能立即同步到主記憶體,以及每次使用前立即從主記憶體重新整理)
  2 禁止進行指令重排序(Volatile修飾的變數,會有一個記憶體屏障,指令重排序的時候,不能把後面的指令,重排序到記憶體屏障之前的位置)重排序是CPU採用了允許將多條指指令不按規定的順序分發開發送給各相應電路單元處理
首先先寫一個單執行緒下的懶漢式單例模式:

package spring.design.pattern.singleton;
 
/**
 * Description:單例模式
 *
 * @author muronglan
 * @version 1.0
 * @date 2018/11/27 10:44
 */
public class LazySingleton {
  /** 1.私有化建構函式 */
  private LazySingleton(){
 
  }
  /** 2.在類的內部建立例項 */
  private LazySingleton lazySingleton;
  /** 3.提供全域性唯一的訪問點 */
  public LazySingleton instance(){
    if(lazySingleton == null){
      lazySingleton = new LazySingleton();
    }
    return lazySingleton;
  }
}

對可能發生執行緒同步問題的程式碼塊加鎖:

package spring.design.pattern.singleton;
 
/**
 * Description:單例模式—懶漢式雙檢索
 *
 * @author muronglan
 * @version 1.0
 * @date 2018/11/27 10:44
 */
public class LazySingleton {
  /** 1.私有化建構函式 */
  private LazySingleton() {}
 
  /** 2.在類的內部建立例項 */
  private volatile LazySingleton lazySingleton;
  /** 3.提供全域性唯一的訪問點 */
  public LazySingleton instance() {
    if (lazySingleton == null) {
      synchronized (LazySingleton.class) {
        if (lazySingleton == null) {
          lazySingleton = new LazySingleton();
        }
      }
    }
    return lazySingleton;
  }
}

總結:
  volatile 關鍵字可以保證變數的可見性和順序性,但是無法保證原子性,Synchronized可以保證原子性,但是由於被Synchronized修飾的程式碼塊上存在同步監視器模型,跑的是單執行緒,且每次執行時需要判斷是否有鎖,獲取同步鎖,所以執行效能上比較差。這裡可以把每一條指令,想象成一個原子。假設每條指令之間都會發生執行緒同步問題,然後再加鎖。如果只是想保證可見性,可以使用volatile ,如果想要保證程式碼塊的原子性,則使用Synchronized

3 靜態內部類的懶漢式:
基於

載入一個類時,其內部類不會同時被載入。一個類被載入,當且僅當其某個靜態成員(靜態域、構造器、靜態方法等)被呼叫時發生。

的結論,可以使用靜態內部類的方法,進行單例模式物件的初始化操作

package spring.design.pattern.singleton;
 
/**
 * Description:靜態內部類實現單例模式
 *
 * @author muronglan
 * @version 1.0
 * @date 2018/11/27 11:25
 */
public class StaticSingleton {
  /** 1.私有化建構函式 */
  private StaticSingleton() {}
  /** 2.在類的內部建立例項 */
  private static class LazyHolder{
    private static StaticSingleton staticSingleton = new StaticSingleton();
  }
  /** 3.提供全域性唯一的訪問點 */
  public StaticSingleton instance(){
    return LazyHolder.staticSingleton;
  }
}

4 單例模式的最小化實現:

package spring.design.pattern.singleton;
 
/**
 * Description:單例模式的最小實現.
 *
 * @author muronglan
 * @version 1.0
 * @date 2018/11/27 11:31
 */
public enum MinSingleton {
  min,
}

參考:
https://mp.weixin.qq.com/s/dU_Mzz76h-qQZvrgeSe44g
單例設計模式Java3y
《Head First設計模式》
http://www.cnblogs.com/zhangzt/p/4350556.html
靜態類和單例模式的區別
https://blog.csdn.net/zmx729618/article/details/69227762
內部類載入順序及靜態內部類單例模式
https://www.jianshu.com/p/157279e6efdb
讓你徹底理解volatile
https://www.cnblogs.com/zhengbin/p/5654805.html
Java中的volatile詳解
https://zhuanlan.zhihu.com/p/29723536
單例模式小結