簡介
單例模式是屬于建立型模式的一種(另外兩種分别是結構型模式,行為型模式).是設計模式中最為簡單的一種.
英文單詞Singleton的數學含義是"有且僅有一個元素的集合".
從實作層面看, 由類自身管理自己的唯一對象,這個類提供了通路該對象的方式,可以直接通路,不需要執行個體化(使用new).
動機
設計模式中的Singleton的目的是使類的對象在應用程式中保持唯一,這在某些應用場合非常重要,比如檔案系統的資料總管,又比如應用的日志,應用程式配置等. 保持唯一執行個體有利于節約系統資源, 同時提升了應用程式性能,避免對資源的多重占用.
實作
一. 簡單實作(線程不安全)-懶漢模式
public class Singleton{
private static Singleton onlyOneInstance;
private Singleton(){
}
public static Singleton getOnlyOneInstance(){
if(onlyOneInstance == null){
onlyOneInstance = new Singleton();
}
return onlyOneInstance;
}
}
如果有多個線程同時運作到
if(onlyOneInstance == null)
,并且此時執行個體為null,那多個線程會執行
onlyOneInstance = new Singleton();
語句,導緻執行個體化多次.
二. 簡單實作(線程安全)-懶漢模式
public static synchronized Singleton getOnlyOneInstance(){
if(onlyOneInstance == null){
onlyOneInstance = new Singleton();
}
return onlyOneInstance;
}
在方法
getOnlyOneInstance()
上增加同步修飾符
synchronized
,這樣可以保證同一時間隻有一個線程通路方法,進而確定不會發生多次執行個體化.
問題是多個線程通路該方法時會發生阻塞,導緻其他線程會在該方法上等待,是以性能上有損耗.
三. 最簡單實作(線程安全)-餓漢模式
private static Singleton onlyOneInstance = new Singleton();
這個實作是線程安全的, 但同時也沒有延遲加載帶來的節約資源的好處.
四. 雙重校驗鎖(線程安全)
public class Singleton{
private volatile static Singleton onlyOneInstance;
private Singleton(){
}
public static Singleton getOnlyOneInstance(){
if(onlyOneInstance == null){
synchronized(Singleton.class){
if(onlyOneInstance == null){
onlyOneInstance = new Singleton();
}
}
}
return onlyOneInstance;
}
}
與第二點的代碼比較,本方法将同步鎖放在了方法内部,這樣可以減少部分線程阻塞,因為在第一個判斷
if(onlyOneInstance == null)
時如果執行個體不為null,方法就傳回了.
與第二點的代碼比較,多個線程依然可能會同時進入到
if(onlyOneInstance == null)
,同樣的,此時onlyOneInstance有可能是null,是以需要在
synchronized(Singleton.class)
内部再次判斷
if(onlyOneInstance == null)
onlyOneInstance 使用 volatile 修飾的必須的.解釋這點的原因需要深入到 JVM 的指令重排機制.
onlyOneInstance = new Singleton();
的執行需要分三步:
1. 配置設定記憶體
2. 初始化對象
3. 将 onlyOneInstance 指向配置設定的記憶體位址
由于 JVM 的指令重排特性,上述三步步驟可能會重排為 1>3>2 ,在多線程環境下通路onlyOneInstance時, 有可能會得到一個未被初始化的執行個體.而valatile關鍵字可以阻止 JVM 的指令重排,進而保證多線程環境下正常運作.
五. 靜态内部類實作
public class Singleton{
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance(){
return SingletonHolder.INSTANCE;
}
}
這種實作利用了 JVM 保持線程安全性, 同時也具備了延遲加載的好處.
六. 枚舉實作
public enum Singleton{
INSTANCE
}
單例的最佳實踐, 一則實作簡單, 二則面對複雜的序列化或反射攻擊的時候能夠防止執行個體化多次.
反射攻擊:可以使用反射原理, 通過setAccessible()方法提升構造函數的通路級别為public,然後通過new執行個體化對象.
對于序列化和反序列化,因為每一個枚舉類型和枚舉變量在JVM中都是唯一的,即Java在序列化和反序列化枚舉時做了特殊的規定,枚舉的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被編譯器禁用的,是以也不存在實作序列化接口後調用readObject會破壞單例的問題。
枚舉為什麼是最佳實踐?
首先, 枚舉是class實作的,也就是說它可以有成員變量及成員函數,這是我們可以使用它來實作單例的基礎. 另外,枚舉繼承了Enum類,它不能作為子類繼承其他類,但可以實作接口, 它也不能被其他類繼承, 枚舉編譯後的類會添加final修飾符, 編譯後的代碼如下:
public final class EnumClass extends Enum{
}
其次,枚舉有且僅有private構造器,這點滿足單例模式的條件.而枚舉值的初始化是在靜态代碼中進行.枚舉實作的單例模式不具備懶加載的作用.