Java設(shè)計(jì)模式之——單例模式(5種單例類型)
單例模式是一種創(chuàng)建型設(shè)計(jì)模式,它可以保證一個(gè)類只有一個(gè)實(shí)例,并提供全局訪問點(diǎn)。單例模式在實(shí)際開發(fā)中經(jīng)常使用,可以避免多個(gè)實(shí)例引起的資源浪費(fèi)和同步問題。
根據(jù)實(shí)現(xiàn)方式的不同,單例模式可以分為以下幾種分類:
1. 餓漢式單例模式
餓漢式單例模式是指在類加載時(shí)就創(chuàng)建了單例對象,因此在調(diào)用時(shí)不需要再創(chuàng)建對象,直接使用即可。這種實(shí)現(xiàn)方式比較簡單,但是在某些情況下可能會(huì)造成資源浪費(fèi)。
2. 懶漢式單例模式
懶漢式單例模式是指在調(diào)用時(shí)才創(chuàng)建單例對象,這種實(shí)現(xiàn)方式避免了資源浪費(fèi),但是需要注意線程安全問題。
3. 雙重檢查鎖單例模式
雙重檢查鎖單例模式是指在懶漢式單例模式的基礎(chǔ)上加入了雙重檢查鎖機(jī)制,可以保證線程安全,同時(shí)也避免了資源浪費(fèi)。
4. 靜態(tài)內(nèi)部類單例模式
靜態(tài)內(nèi)部類單例模式是指將單例對象作為靜態(tài)內(nèi)部類的一個(gè)靜態(tài)變量,這種實(shí)現(xiàn)方式可以保證線程安全,同時(shí)也可以避免資源浪費(fèi)。
5. 枚舉單例模式
枚舉單例模式是指將單例對象定義為一個(gè)枚舉類型,這種實(shí)現(xiàn)方式可以保證線程安全,同時(shí)也可以防止反射和序列化攻擊。
下面是配合源碼的講解:
1. 餓漢式單例模式
餓漢式單例模式是指在類加載時(shí)就創(chuàng)建了單例對象,因此在調(diào)用時(shí)不需要再創(chuàng)建對象,直接使用即可。這種實(shí)現(xiàn)方式比較簡單,但是在某些情況下可能會(huì)造成資源浪費(fèi)。
下面是一個(gè)簡單的餓漢式單例模式的示例代碼:
```java
public class Singleton {
? ? private static final Singleton instance = new Singleton();
? ? private Singleton() {
? ? ? ? // 私有化構(gòu)造方法
? ? }
? ? public static Singleton getInstance() {
? ? ? ? return instance;
? ? }
? ? // 其他業(yè)務(wù)方法
}
```
上面的代碼中,我們定義了一個(gè) `Singleton` 類,并在類加載時(shí)創(chuàng)建了一個(gè)單例對象 `instance`。`instance` 使用 `private static final` 修飾,表示它是一個(gè)私有的靜態(tài)常量,其他類不能修改它的值。
`Singleton` 的構(gòu)造方法被私有化,這樣其他類就無法通過 `new` 關(guān)鍵字來創(chuàng)建 `Singleton` 的實(shí)例。
`getInstance` 方法返回 `instance` 對象,這樣其他類可以通過 `Singleton.getInstance()` 來獲取單例對象。
2. 懶漢式單例模式
懶漢式單例模式是指在調(diào)用時(shí)才創(chuàng)建單例對象,這種實(shí)現(xiàn)方式避免了資源浪費(fèi),但是需要注意線程安全問題。
下面是一個(gè)簡單的懶漢式單例模式的示例代碼:
```java
public class Singleton {
? ? private static Singleton instance = null;
? ? private Singleton() {
? ? ? ? // 私有化構(gòu)造方法
? ? }
? ? public static synchronized Singleton getInstance() {
? ? ? ? if (instance == null) {
? ? ? ? ? ? instance = new Singleton();
? ? ? ? }
? ? ? ? return instance;
? ? }
? ? // 其他業(yè)務(wù)方法
}
```
上面的代碼中,我們定義了一個(gè) `Singleton` 類,并使用一個(gè)靜態(tài)變量 `instance` 來保存單例對象。在 `getInstance` 方法中,我們首先判斷 `instance` 是否為 `null`,如果是則創(chuàng)建一個(gè)新的 `Singleton` 對象,并將其賦值給 `instance` 變量。由于 `getInstance` 方法是 `synchronized` 的,所以可以保證線程安全。
需要注意的是,懶漢式單例模式可能存在性能問題,因?yàn)槊看握{(diào)用 `getInstance` 方法都需要進(jìn)行線程同步,這會(huì)影響程序的性能。為了避免這個(gè)問題,可以使用雙重檢查鎖單例模式。
3. 雙重檢查鎖單例模式
雙重檢查鎖單例模式是指在懶漢式單例模式的基礎(chǔ)上加入了雙重檢查鎖機(jī)制,可以保證線程安全,同時(shí)也避免了資源浪費(fèi)。
下面是一個(gè)簡單的雙重檢查鎖單例模式的示例代碼:
```java
public class Singleton {
? ? private static volatile Singleton instance = null;
? ? private Singleton() {
? ? ? ? // 私有化構(gòu)造方法
? ? }
? ? public static Singleton getInstance() {
? ? ? ? if (instance == null) {
? ? ? ? ? ? synchronized (Singleton.class) {
? ? ? ? ? ? ? ? if (instance == null) {
? ? ? ? ? ? ? ? ? ? instance = new Singleton();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return instance;
? ? }
? ? // 其他業(yè)務(wù)方法
}
```
上面的代碼中,我們使用了 `volatile` 關(guān)鍵字來保證 `instance` 變量的可見性。在 `getInstance` 方法中,我們首先判斷 `instance` 是否為 `null`,如果是則進(jìn)入同步塊。在同步塊中,我們再次判斷 `instance` 是否為 `null`,如果是則創(chuàng)建一個(gè)新的 `Singleton` 對象,并將其賦值給 `instance` 變量。由于使用了雙重檢查鎖機(jī)制,所以可以保證線程安全。
需要注意的是,雙重檢查鎖單例模式在多線程環(huán)境下仍然可能存在問題,因?yàn)樵谀承┣闆r下可能會(huì)出現(xiàn)指令重排的情況。為了避免這個(gè)問題,可以將 `instance` 變量聲明為 `volatile`,這樣可以禁止指令重排。
4. 靜態(tài)內(nèi)部類單例模式
靜態(tài)內(nèi)部類單例模式是指將單例對象作為靜態(tài)內(nèi)部類的一個(gè)靜態(tài)變量,這種實(shí)現(xiàn)方式可以保證線程安全,同時(shí)也可以避免資源浪費(fèi)。
下面是一個(gè)簡單的靜態(tài)內(nèi)部類單例模式的示例代碼:
```java
public class Singleton {
? ? private Singleton() {
? ? ? ? // 私有化構(gòu)造方法
? ? }
? ? public static Singleton getInstance() {
? ? ? ? return SingletonHolder.instance;
? ? }
? ? private static class SingletonHolder {
? ? ? ? private static final Singleton instance = new Singleton();
? ? }
? ? // 其他業(yè)務(wù)方法
}
```
上面的代碼中,我們定義了一個(gè) `Singleton` 類,并將單例對象作為靜態(tài)內(nèi)部類 `SingletonHolder` 的一個(gè)靜態(tài)變量。在 `getInstance` 方法中,我們直接返回 `SingletonHolder.instance`,這樣就可以獲取到單例對象。
由于靜態(tài)內(nèi)部類只會(huì)在第一次使用時(shí)才會(huì)被加載,所以可以保證線程安全。而且由于單例對象是在靜態(tài)內(nèi)部類中創(chuàng)建的,所以可以避免資源浪費(fèi)。
5. 枚舉單例模式
枚舉單例模式是指將單例對象定義為一個(gè)枚舉類型,這種實(shí)現(xiàn)方式可以保證線程安全,同時(shí)也可以防止反射和序列化攻擊。
下面是一個(gè)簡單的枚舉單例模式的示例代碼:
```java
public enum Singleton {
? ? INSTANCE;
? ? // 其他業(yè)務(wù)方法
}
```
上面的代碼中,我們定義了一個(gè)枚舉類型 `Singleton`,其中 `INSTANCE` 就是單例對象。由于枚舉類型只會(huì)被加載一次,所以可以保證線程安全。
由于枚舉類型的構(gòu)造方法是私有化的,所以無法通過反射來創(chuàng)建枚舉類型的實(shí)例,從而避免了反射攻擊。同時(shí),枚舉類型的序列化和反序列化也是由 JVM 自動(dòng)完成的,所以可以避免序列化攻擊。