Java設(shè)計(jì)模式-單例模式
一、前言
單例模式是一種設(shè)計(jì)模式,它確保一個(gè)類只能創(chuàng)建一個(gè)實(shí)例,并提供一種全局訪問這個(gè)實(shí)例的方式。在Java中,單例模式可以通過多種方式來實(shí)現(xiàn),其中最常見的是使用私有構(gòu)造函數(shù)和靜態(tài)方法實(shí)現(xiàn)

二、基本語法
在Java中,實(shí)現(xiàn)單例模式的方式有多種,其中最常見的實(shí)現(xiàn)方式包括以下幾種:
1、懶漢式單例模式
懶漢式單例模式指的是在第一次使用單例對(duì)象時(shí)才創(chuàng)建實(shí)例。具體實(shí)現(xiàn)方式是在getInstance()方法中判斷實(shí)例是否已經(jīng)被創(chuàng)建,如果沒有則創(chuàng)建一個(gè)新實(shí)例并返回。懶漢式單例模式的缺點(diǎn)是線程不安全,在多線程環(huán)境下可能會(huì)創(chuàng)建多個(gè)實(shí)例。
public class Singleton {
? ?private static Singleton instance;
? ?
? ?private Singleton() {
? ? ? ?// 私有構(gòu)造函數(shù)
? ?}
? ?
? ?public static Singleton getInstance() {
? ? ? ?if (instance == null) {
? ? ? ? ? ?instance = new Singleton();
? ? ? ?}
? ? ? ?return instance;
? ?}
}
2、餓漢式單例模式
餓漢式單例模式指的是在類加載時(shí)就創(chuàng)建實(shí)例,因此也被稱為靜態(tài)單例模式。具體實(shí)現(xiàn)方式是將實(shí)例化語句放在靜態(tài)代碼塊中。由于在類加載時(shí)就創(chuàng)建了實(shí)例,因此不存在線程安全性問題。
public class Singleton {
? ?private static Singleton instance = new Singleton();
? ?
? ?private Singleton() {
? ? ? ?// 私有構(gòu)造函數(shù)
? ?}
? ?
? ?public static Singleton getInstance() {
? ? ? ?return instance;
? ?}
}
3、雙重檢驗(yàn)鎖單例模式
雙重檢驗(yàn)鎖單例模式是一種線程安全的單例模式實(shí)現(xiàn)方式,它通過使用synchronized關(guān)鍵字來確保線程安全性。具體實(shí)現(xiàn)方式是在getInstance()方法中添加雙重檢驗(yàn)鎖,這可以避免不必要的鎖競爭和實(shí)例化。
public class Singleton {
? ?private static volatile Singleton instance;
? ?
? ?private Singleton() {
? ? ? ?// 私有構(gòu)造函數(shù)
? ?}
? ?
? ?public static Singleton getInstance() {
? ? ? ?if (instance == null) {
? ? ? ? ? ?synchronized (Singleton.class) {
? ? ? ? ? ? ? ?if (instance == null) {
? ? ? ? ? ? ? ? ? ?instance = new Singleton();
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?return instance;
? ?}
}
4、靜態(tài)內(nèi)部類單例模式
靜態(tài)內(nèi)部類單例模式是一種比較常用的單例模式實(shí)現(xiàn)方式,它利用了靜態(tài)內(nèi)部類只會(huì)在被使用時(shí)才會(huì)加載的特點(diǎn),從而避免了餓漢式單例模式的資源浪費(fèi)和懶漢式單例模式的線程不安全問題。
public class Singleton {
? ?private static class SingletonHolder {
? ? ? ?private static final Singleton INSTANCE = new Singleton();
? ?}
? ?
? ?private Singleton() {
? ? ? ?// 私有構(gòu)造函數(shù)
? ?}
? ?
? ?public static Singleton getInstance() {
? ? ? ?return SingletonHolder.INSTANCE;
? ?}
}
5、枚舉單例模式
枚舉單例模式是一種更為簡單和安全的單例模式實(shí)現(xiàn)方式,它利用了Java中枚舉類型本身就是單例的特點(diǎn)。枚舉單例模式是一種天然線程安全的單例模式實(shí)現(xiàn)方式,而且可以防止反射和序列化等攻擊。
public enum Singleton {
? ?INSTANCE;
? ?
? ?// 其他方法
}
6、ThreadLocal單例模式
ThreadLocal單例模式是一種可以在多線程環(huán)境下確保單例對(duì)象的線程安全單例模式實(shí)現(xiàn)方式。具體實(shí)現(xiàn)方式是在ThreadLocal中保存單例對(duì)象,每個(gè)線程都有自己的ThreadLocal副本,從而避免了線程安全性問題。
public class Singleton {
? ?private static final ThreadLocal<Singleton> INSTANCE = new ThreadLocal<Singleton>() {
? ? ? ?@Override
? ? ? ?protected Singleton initialValue() {
? ? ? ? ? ?return new Singleton();
? ? ? ?}
? ?};
? ?
? ?private Singleton() {
? ? ? ?// 私有構(gòu)造函數(shù)
? ?}
? ?
? ?public static Singleton getInstance() {
? ? ? ?return INSTANCE.get();
? ?}
}
7、注冊(cè)式單例模式
注冊(cè)式單例模式指的是通過一個(gè)注冊(cè)表來管理所有單例對(duì)象,從而實(shí)現(xiàn)單例模式。具體實(shí)現(xiàn)方式是在一個(gè)靜態(tài)的Map中保存所有單例對(duì)象,然后在需要使用單例對(duì)象時(shí)通過Map來獲取。
public class Singleton {
? ?private static Map<String, Singleton> instances = new HashMap<>();
? ?
? ?private Singleton() {
? ? ? ?// 私有構(gòu)造函數(shù)
? ?}
? ?
? ?public static Singleton getInstance(String name) {
? ? ? ?if (!instances.containsKey(name)) {
? ? ? ? ? ?instances.put(name, new Singleton());
? ? ? ?}
? ? ? ?return instances.get(name);
? ?}
}
三、使用場景
單例模式通常在需要確保全局只有一個(gè)實(shí)例的場景中使用,例如:
線程池:在多線程環(huán)境下,線程池需要保證只有一個(gè)實(shí)例。
數(shù)據(jù)庫連接池:同樣地,數(shù)據(jù)庫連接池也需要保證只有一個(gè)實(shí)例。
日志對(duì)象:日志對(duì)象通常是全局可見的,因此需要保證只有一個(gè)實(shí)例。
配置文件:在某些情況下,需要全局共享的配置文件也需要保證只有一個(gè)實(shí)例。
四、使用示例
下面是一個(gè)簡單的例子,演示如何使用單例模式實(shí)現(xiàn)線程池:
public class ThreadPool {
? ?private static ThreadPool instance;
? ?
? ?private ThreadPool() {
? ? ? ?// 初始化線程池
? ?}
? ?
? ?public static synchronized ThreadPool getInstance() {
? ? ? ?if (instance == null) {
? ? ? ? ? ?instance = new ThreadPool();
? ? ? ?}
? ? ? ?return instance;
? ?}
? ?
? ?// 線程池相關(guān)的方法
}
在上述代碼中,我們使用synchronized關(guān)鍵字來保證getInstance()方法的線程安全性。這意味著每次只有一個(gè)線程可以訪問getInstance()方法,從而避免了多個(gè)線程同時(shí)創(chuàng)建線程池實(shí)例的問題。
四、常見問題
單例模式的實(shí)現(xiàn)有一些常見問題,需要注意:
線程安全性:如上所述,如果多個(gè)線程同時(shí)訪問getInstance()方法,可能會(huì)導(dǎo)致多個(gè)實(shí)例的創(chuàng)建。因此,需要確保getInstance()方法是線程安全的,可以通過synchronized關(guān)鍵字來實(shí)現(xiàn)。
序列化問題:如果單例類實(shí)現(xiàn)了Serializable接口,那么在反序列化時(shí)可能會(huì)創(chuàng)建多個(gè)實(shí)例。解決方法是在類中添加readResolve()方法,并返回單例實(shí)例。
反射問題:通過反射機(jī)制,可以調(diào)用私有構(gòu)造函數(shù)創(chuàng)建實(shí)例。解決方法是在構(gòu)造函數(shù)中添加判斷,如果已經(jīng)存在實(shí)例則拋出異常