更多知識,請移步我的小破站:http://hellofriend.top
什麼是單例設計模式?
單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。
通過單例模式可以保證系統中,應用該模式的類只有一個實例。即一個類只有一個對象實例。
在Java語言中,單例帶來了兩大好處:
- 對於頻繁使用的對象(數據源、Session工廠),可以省略創建對象所花費的時間,這對於重量級的對象而言,是非常可觀的一筆系統開銷。
- 由於
new
操作的次數減少,因而對系統內存的使用頻率也會降低,這將減輕GC壓力,縮短GC停頓時間。
具體實現
需要:
(1)將構造方法私有化,使其不能在類的外部通過new
關鍵字實例化該類對象。
(2)在該類內部產生一個唯一的實例化對象,並且將其封裝爲 private static
類型。
(3)定義一個靜態方法返回這個唯一對象。
實現一:餓漢式 / 靜態常量
- 立即加載就是使用類的時候已經將對象創建完畢(不管以後會不會使用到該實例化對象,先創建了)。常見的實現辦法就是直接 new 實例化。
代碼如下:
public class HungrySingletonStaticInstance {
public static void main(String[] args) {
System.out.println("======HungrySingletonStaticInstance======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
// 將自身實例化對象設置爲一個屬性,並用static、final修飾
private final static Singleton instance = new Singleton();
// 構造方法私有化
private Singleton() {}
// 提供靜態方法返回該實例
public static Singleton getInstance() {
return instance;
}
}
“餓漢模式” 的優缺點分析:
- 優點:實現起來簡單,沒有多線程同步問題。
- 缺點:當類
Singleton
被加載的時候,會初始化static
的instance
,靜態變量被創建並分配內存空間,從這以後,這個static
的instance
對象便一直佔着這段內存,可能造成內存浪費(即便你還沒有用到這個實例)。當類被卸載時,靜態變量被摧毀,並釋放所佔有內存,在某些特定條件下會耗費內存。 - 補充:如果方法內有其他
static
方法,調用該方法此類也會加載初始化。
實現二:餓漢式 / 靜態代碼塊
public class HungrySingletonStaticBlock {
public static void main(String[] args) {
System.out.println("======HungrySingletonStaticBlock======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
- 如果方法內有其他
static
方法,調用該方法此類也會加載初始化。
實現三:懶漢式 / 線程不安全
- 延遲加載就是調用
getInstance()
方法時實例才被創建(先不急着實例化出對象,等要用的時候才創建出來)。常見的實現方法就是在getInstance()
方法中進行 new 實例化。
public class LazyLoadingSingletonThreadUnSafe {
public static void main(String[] args) {
System.out.println("======LazySingletonThreadUnsafe======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
// 將自身實例化對象設置爲一個屬性,並用static修飾
private static Singleton instance;
// 構造方法私有化
private Singleton() {}
// 靜態方法返回該實例
public static Singleton getInstance() {
if (instance == null) {
//TODO 線程在這裏被阻塞,則此時對象沒有被創建,UnSafe
instance = new Singleton();
}
return instance;
}
}
“懶漢模式” 的優缺點分析:
- 優點:實現起來比較簡單,當類
Singleton
被加載的時候,靜態變量static
的instance
未被創建並分配內存空間,當getInstance()
方法第一次被調用時,初始化instance
變量,並分配內存,因此在某些特定條件下會節約了內存。 - 缺點:在多線程環境中,這種實完現方法是錯誤的,不能保證單例的狀態。
- 補充:如果方法內有其他
static
方法,調用該方法此類不會加載初始化。
實現四:懶漢式 / 線程安全 Sync
- 靜態方法返回該實例,加
Synchronized
關鍵字實現同步。
public class LazyLoadingSingletonSync {
public static void main(String[] args) {
System.out.println("======LazyLoadingSingletonSync======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
// 將自身實例化對象設置爲一個屬性,並用static修飾
private static Singleton instance;
// 構造方法私有化
private Singleton() {}
// 靜態方法返回該實例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
線程安全的“懶漢模式”(加鎖)的優缺點:
- 優點:在多線程情形下,保證了“懶漢模式”的線程安全。
- 缺點:在多線程情形下,
synchronized
方法通常效率低,顯然這不是最佳的實現方案。 - 補充:如果方法內有其他
static
方法,調用該方法此類不會加載初始化。
實現五:DCL雙重檢查鎖定機制
(DCL:Double Checked Locking)
public class LazyLoadingSingletonDCL {
public static void main(String[] args) {
System.out.println("======LazyLoadingSingletonDCL======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
// 將自身實例化對象設置爲一個屬性,並用 volatile、static 修飾
private volatile static Singleton instance;
// 構造方法私有化
private Singleton() {}
// 靜態方法返回該實例
public static Singleton getInstance() {
// 第一次檢查instance是否被實例化出來
if (instance == null) {
synchronized (Singleton.class) {
// 某個線程取得了類鎖,實例化對象前第二次檢查 instance 是否已經被實例化
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 該方法是單例模式推薦的實現方式。內存佔用率高,效率高,線程安全,多線程操作原子性。
- 如果方法內有其他
static
方法,調用該方法此類不會加載初始化。
實現六:靜態內部類
public class StaticInternalSingleton {
public static void main(String[] args) {
System.out.println("======StaticInternalSingleton======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
//加載 Singleton 類時並不會加載內部類
private static final Singleton INSTANCE = new Singleton();
}
}
- 第一次加載
Singleton
類時並不會加載內部類初始化Instance
,只有第一次調用getInstance
方法時虛擬機加載SingletonHolder
並初始化Instance
,這樣不僅能確保線程安全也能保證Singleton
類的唯一性,所以推薦使用靜態內部類單例模式。
實現七:枚舉單例
該方法藉助 JDK 1.5 中添加的枚舉來實現單例模式。不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。
public class EnumSingleton {
public static void main(String[] args) {
System.out.println("======EnumSingleton======");
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
instance1.method();
instance2.method();
}
}
enum Singleton {
INSTANCE;
public void method() {
System.out.println("EnumSingleton");
}
}
JDK 中單例模式的應用之 Runtime 類
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
//TODO 私有化構造器
private Runtime() {}
很顯然,JDK 中的 Runtime 類使用的是本文的 實現一:餓漢式 / 靜態常量來實現的,由於 Runtime 類在程序運行中一定會使用到,所以並不會帶來餓漢式的空間浪費問題。
小總結
(1)單例模式保證了系統內存中該類只存在一個對象,節省了系統資源,對於一些需要頻繁創建銷燬的對象,使用單例模式可以提高系統的性能。
(2)當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法,而不是使用new。