## 一. 什么是單例設計模式?
>單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例。
**類結構圖**

**具體實現**
```
1. 將構造方法私有化,使其不能在類的外部通過 new關鍵字實例化該類對象。
2. 在該類內部產生一個唯一的實例化對象,并且將其封裝為 private static 類型。
3. 定義一個靜態方法返回這個唯一對象。
```
## 二. 單例設計模式實現形式
>**餓漢式**,從名字上也很好理解,就是“比較勤”,實例在初始化的時候就已經建好了,不管你有沒有用到,都先建好了再說。好處是沒有線程安全的問題,壞處是浪費內存空間。
>**懶漢式**,顧名思義就是實例在用到的時候才去創建,“比較懶”,用的時候才去檢查有沒有實例,如果有則返回,沒有則新建。有新線程安全和線程不安全兩種寫法,區別就是 Synchronized 關鍵字。
#### **1、餓漢式實現方式一(推薦)**
>類加載到內存后,就實例化一個單例,JVM保證線程安全;簡單使用;
但也有缺點:不管用到與否,類裝載時就完成實例化
```
/**
* 餓漢式
* 類加載到內存后,就實例化一個單例,JVM保證線程安全
* 簡單實用,推薦使用!
* 唯一缺點:不管用到與否,類裝載時就完成實例化
* Class.forName("")
* (話說你不用的,你裝載它干啥)
*/
public class Singleton_01 {
private static final Singleton_01 INSTANCE = new Singleton_01();
// 構造方法私有
private Singleton_01() {}
public static Singleton_01 getInstance() {
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
Singleton_01 singleton01 = Singleton_01.getInstance();
Singleton_01 singleton02 = Singleton_01.getInstance();
System.out.println(singleton01 == singleton02);
}
}
```
#### **2、餓漢式實現方式二**
與上述?Singleton_01 一樣,通過靜態代碼塊實現單例的實例化
```
/**
* 跟01是一個意思
*/
public class Singleton_02 {
private static final Singleton_02 INSTANCE;
// 通過靜態代碼塊實現單例的實例化
static {
INSTANCE = new Singleton_02();
}
// 構造方法私有
private Singleton_02() {}
public static Singleton_02 getInstance() {
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
Singleton_02 singleton01 = Singleton_02.getInstance();
Singleton_02 singleton02 = Singleton_02.getInstance();
System.out.println(singleton01 == singleton02);
}
}
```
#### **3、懶漢式實現方式一(線程不安全)**
>懶漢式即需要使用類的實例化時才創建對象,但下述懶漢式卻帶來了線程不安全的問題;通過使用多線程也獲取
單例對象的 hashCode 非常容易拿到不一樣的hashCode,證明單例對象不是同一個,即存在線程不安全問題。
```
/**
* lazy loading
* 也稱懶漢式
* 雖然達到了按需初始化的目的,但卻帶來線程不安全的問題
*/
public class Singleton_03 {
private static Singleton_03 INSTANCE;
// 構造方法私有
private Singleton_03() {}
public static Singleton_03 getInstance() {
if (INSTANCE == null) {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (Exception e){
e.printStackTrace();
}
INSTANCE = new Singleton_03();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Singleton_03.getInstance().hashCode());
}).start();
}
}
}
```
#### **4、懶漢式實現方式二(線程安全,但效率低)**
>運行過 【懶漢式實現方式一】就會發現存在多線程不安全的問題,因為可以引入 Synchronized 同步方法來解決此問題,但卻帶來了效率下降問題。
補充說明下:Synchronized在這里是修飾靜態同步方法,實際上是對該類 Class 對象加鎖,俗稱“類鎖”。
```
/**
* lazy loading
* 也稱懶漢式
* 雖然達到了按需初始化的目的,但卻帶來線程不安全的問題
* 線程當安全的問題,可以通過 synchronized 解決,但也帶來效率下降
*/
public class Singleton_04 {
private static Singleton_04 INSTANCE;
// 構造方法私有
private Singleton_04() {}
// 鎖住 Singleton_04 對象
public static synchronized Singleton_04 getInstance() {
if (INSTANCE == null) {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (Exception e){
e.printStackTrace();
}
INSTANCE = new Singleton_04();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Singleton_04.getInstance().hashCode());
}).start();
}
}
}
```
#### **5、懶漢實現方式三(線程不安全)**
>運行過 【懶漢式實現方式二】就會發現引入 Synchronized 同步方法來解決多線程不安全問題,但卻帶來了效率下降問題。為此,將 Synchronized 同步鎖的粒度減小,發現效率是提升了,但依然沒有解決多線程不安全問題
```
/**
* lazy loading
* 也稱懶漢式
* 雖然達到了按需初始化的目的,但卻帶來線程不安全的問題
* 可以通過 synchronized 解決,但也帶來效率下降
* 通過減少 synchronized 同步鎖粒度,發現多線程不安全問題依然存在
*/
public class Singleton_05 {
private static Singleton_05 INSTANCE;
// 構造方法私有
private Singleton_05() {}
public static Singleton_05 getInstance() {
if (INSTANCE == null) {
// 妄圖通過減少同步代碼塊的方式提高效率,然后不可能
synchronized (Singleton_05.class) {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (Exception e){
e.printStackTrace();
}
INSTANCE = new Singleton_05();
}
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Singleton_05.getInstance().hashCode());
}).start();
}
}
}
```
#### **6、懶漢實現方式四 - DCL 雙端檢查 + volatile(線程安全)**
>減少 synchronize 效率是可以的,但依然存在多線程不安全問題;通過引入 DCL (Double Check Lock 雙端檢查方式) + volatile (禁止指令重排),解決了多線程安全問題,同時還提高了效率。
>DCL + Volatile,綜合了懶漢式和餓漢式兩者的優缺點整合而成。看下面代碼實現中,特點是在 Synchronized 關鍵字內外都加了一層 if 條件判斷,這樣既保證了線程安全,又比直接上鎖提高了執行效率,還節省了內存空間。在這里使用 volatile 會或多或少的影響性能,但考慮到程序的正確性,犧牲這點性能還是值得的。DCL 優點是資源利用率高,第一次執行 getInstance() 時單例對象才被實例化,效率高。缺點是第一次加載時反應稍慢一些,在高并發環境下也有一定的缺陷,雖然發生的概率很小。DCL雖然在一定程度解決了資源的消耗和多余的同步,線程安全等問題,但是它還是在某些情況會出現失效的問題,也就是DCL 失效,在《java并發編程實踐》一書建議用**靜態內部類單例模式**來替代DCL.
```
/**
* lazy loading
* 也稱懶漢式
* 雖然達到了按需初始化的目的,但卻帶來線程不安全的問題
* 可以通過 synchronized 解決,但也帶來效率下降
* 通過減少 synchronized 同步鎖粒度,發現多線程不安全問題依然存在
* 通過采用 DCL 即 Double Check Lock 雙端檢查機制,出現多線程不安全問題概率大大降低了,但還有存在多線程不安全問題
*/
public class Singleton_06 {
private static volatile Singleton_06 INSTANCE;
// 構造方法私有
private Singleton_06() {}
public static Singleton_06 getInstance() {
if (INSTANCE == null) {
// 雙重檢查
synchronized (Singleton_06.class) {
if (INSTANCE == null) {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (Exception e){
e.printStackTrace();
}
INSTANCE = new Singleton_06();
}
}
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Singleton_06.getInstance().hashCode());
}).start();
}
}
}
```
#### **7、靜態內部類方式,JVM 保證單例(推薦)**
>通過 JVM 加載外部類時不會加載內部類,這樣可以實現懶加載,完美寫法之一,比第一種要好一些
第一次加載 Singleton 類時并不會初始化 INSTANCE,只有第一次調用 getInstance 方法時虛擬機加載 Singleton\_07\_Holder 并初始化 INSTANCE,這樣不僅能確保線程安全也能保證?Singleton 類的唯一性,所以推薦使用靜態內部類單例模式。
```
/**
* 靜態內部類方式
* JVM 保證單例
* 加載外部類時不會加載內部類,這樣可以實現懶加載
*
* 完美寫法之一,比第一種要好一些
*/
public class Singleton_07 {
// 構造方法私有
private Singleton_07() {}
private static class Singleton_07_Holder {
private final static Singleton_07 INSTANCE = new Singleton_07();
}
// 不但實現了懶加載,而且只加載一次
// Singleton_07如果實例化了,Singleton_07_Holder也不會實例化,只有在getInstance被調用時才加載
public static Singleton_07 getInstance() {
return Singleton_07_Holder.INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Singleton_07.getInstance().hashCode());
}).start();
}
}
}
```
#### **8、通過枚舉實現單例模式(不推薦)**
>枚舉的方式是比較少見的一種實現方式,但是看上面的代碼實現,卻更簡潔清晰。不僅可以解決線程同步,還可以防止反序列化.
>默認枚舉實例的創建是線程安全的,并且在任何情況下都是單例,上述講的幾種單例模式實現在,有一種情況下他們會重新創建對象,那就是反序化,將一個單例實例對象寫到磁盤再讀回來,從而獲得了一個實例。反序列化操作提供了 readResolve 方法,這個方法可以讓開發人員控制對象的反序列化。在上述幾個方法示例中如果要杜絕單例對象被反序列化是重新生成對象。
>**枚舉單例的優點就是簡單,但是大部分應用開發很少用枚舉,可讀性并不是很高,不建議用。**
```
/**
* 不僅可以解決線程同步,還可以防止反序列化
*/
public enum Singleton_08 {
INSTANCE;
public void m() {}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Singleton_08.INSTANCE.hashCode());
}).start();
}
}
}
```
## 三. 總結
>至于在實際項目中選擇哪種形式的單例模式,取決于你的項目本身,是否有復雜的并發環境,
還是需要控制單例對象的資源消耗。
- 前言
- 第一章 設計七大原則
- 第1節 開閉原則
- 第2節 依賴倒置原則
- 第3節 單一職責原則
- 第4節 接口隔離原則
- 第5節 迪米特法則
- 第6節 里氏替換原則
- 第7節 合成復用原則
- 第二章 簡單工廠模式
- 第1節 使用場景
- 第2節 示例代碼
- 第三章 創建者模式
- 第1節 工廠方法模式
- 第2節 抽象工廠模式
- 第3節 建造者模式
- 第4節 原型模式
- 第5節 單例模式
- 第四章 結構型模式
- 第1節 適配器模式
- 第2節 橋接模式
- 第3節 組合模式
- 第4節 裝飾者模式
- 第5節 外觀模式
- 第6節 享元模式
- 第7節 代理模式
- 第五章 行為模式
- 第1節 責任鏈模式
- 第2節 命令模式
- 第3節 迭代器模式
- 第4節 中介者模式
- 第5節 備忘錄模式
- 第6節 觀察者模式
- 第7節 狀態模式
- 第8節 策略模式
- 第9節 模板方法模式
- 第10節 訪問者模式
- 第11節 解釋器模式