公號:碼農充電站pro
主頁:https://codeshellme.github.iohtml
當咱們須要使得某個類只能有一個實例時,可使用單例模式。java
單例模式(Singleton Design Pattern)保證一個類只能有一個實例,並提供一個全局訪問點。git
單例模式的實現須要三個必要的條件:github
注意:
由於單例類的構造函數是私有的,因此單例類不能被繼承。shell
另外,實現單例類時,還須要考慮三個問題:數據庫
下面介紹五種實現單例模式的方式。設計模式
餓漢式的單例實現比較簡單,其在類加載的時候,靜態實例instance
就已建立並初始化好了。安全
代碼以下:多線程
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton () {} public static Singleton getInstance() { return instance; } }
餓漢式單例優缺點:併發
通常認爲延時加載能夠節省內存資源。可是延時加載是否是真正的好,要看實際的應用場景,而不必定全部的應用場景都須要延時加載。
與餓漢式對應的是懶漢式,懶漢式爲了支持延時加載,將對象的建立延遲到了獲取對象的時候,但爲了線程安全,不得不爲獲取對象的操做加鎖,這就致使了低性能。
而且這把鎖只有在第一次建立對象時有用,而以後每次獲取對象,這把鎖都是一個累贅(雙重檢測對此進行了改進)。
代碼以下:
public class Singleton { private static final Singleton instance; private Singleton () {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
懶漢式單例優缺點:
餓漢式和懶漢式的單例都有缺點,雙重檢測的實現方式解決了這二者的缺點。
雙重檢測將懶漢式中的 synchronized
方法改爲了 synchronized
代碼塊。以下:
public class Singleton { private static Singleton instance; private Singleton () {} public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { // 注意這裏是類級別的鎖 if (instance == null) { // 這裏的檢測避免多線程併發時屢次建立對象 instance = new Singleton(); } } } return instance; } }
這種實現方式在 Java 1.4 及更早的版本中有些問題,就是指令重排序,可能會致使 Singleton
對象被 new
出來,而且賦值給 instance
以後,還沒來得及初始化,就被另外一個線程使用了。
要解決這個問題,須要給 instance
成員變量加上 volatile
關鍵字,從而禁止指令重排序。
而高版本的 Java 已在 JDK 內部解決了這個問題,因此高版本的 Java 不須要關注這個問題。
雙重檢測單例優勢:
用靜態內部類的方式實現單例類,利用了Java 靜態內部類的特性:
代碼以下:
public class Singleton { private Singleton () {} private static class SingletonInner { private static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonInner.instance; } }
SingletonInner
是一個靜態內部類,當外部類 Singleton
被加載的時候,並不會建立 SingletonInner
實例對象。
只有當調用 getInstance()
方法時,SingletonInner
纔會被加載,這個時候纔會建立 instance
。instance
的惟一性、建立過程的線程安全性,都由 JVM 來保證。
靜態內部類單例優勢:
用枚舉來實現單例,是最簡單的方式。這種實現方式經過 Java 枚舉類型自己的特性,保證了實例建立的線程安全性和實例的惟一性。
public enum Singleton { INSTANCE; // 該對象全局惟一 }
上面介紹了5 種單例模式的實現方式,下面做爲對單例模式的擴展,再來介紹一下多例模式以及線程間惟一的單例模式。先來看下多例模式。
單例模式是指,一個類只能建立一個對象。那麼多例模式就是,一個類能夠建立多個對象,可是對象個數能夠控制。
對於多例模式,咱們能夠將類的實例都編上號,而後將實例存放在一個 Map
中。
代碼以下:
public class MultiInstance { // 實例編號 private long instanceNum; // 用於存放實例 private static final Map<Long, MultiInstance> ins = new HashMap<>(); static { // 存放 3 個實例 ins.put(1L, new MultiInstance(1)); ins.put(2L, new MultiInstance(2)); ins.put(3L, new MultiInstance(3)); } private MultiInstance(long n) { this.instanceNum = n; } public MultiInstance getInstance(long n) { return ins.get(n); } }
實際上,Java 中的枚舉就是一個「自然」的多例模式,其中的每一項表明一個實例,以下:
public enum MultiInstance { ONE, TWO, THREE; }
通常狀況下,咱們所說的單例的做用範圍是進程惟一的,就是在一個進程範圍內,一個類只容許建立一個對象,進程內的多個線程之間也是共享同一個實例。
實際上,在Java 中,每一個類加載器都定義了一個命名空間。因此咱們這裏實現的單例是依賴類加載器的,也就是在同一個類加載器中,咱們實現的單例就是真正的單例模式。不然若是有多個類加載器,就會有多個單例出現了。一個解決辦法是:自行指定類加載器,而且指定同一個類加載器。
那麼線程惟一的單例就是,一個實例只能被一個線程擁有,一個進程內的多個線程擁有不一樣的類實例。
咱們一樣能夠用 Map
來實現,代碼以下:
public class ThreadSingleton { private static final ConcurrentHashMap<Long, ThreadSingleton> instances = new ConcurrentHashMap<>(); private ThreadSingleton() {} public static ThreadSingleton getInstance() { Long id = Thread.currentThread().getId(); instances.putIfAbsent(id, new ThreadSingleton()); return instances.get(id); } }
單例模式能夠用來管理一些共享資源,好比數據庫鏈接池,線程池;解決資源衝突問題,好比日誌打印。節省內存空間,好比配置信息類。
(本節完。)
推薦閱讀:
歡迎關注做者公衆號,獲取更多技術乾貨。