在面向對象編程中,單例指的是一個類只允許創建一個實例,並且提供一個全局訪問的接口。單例模式主要解決的問題是在多線程環境下,如何保證一個類只有一個實例,並且能夠在全局範圍內訪問該實例。懶漢式單例模式是其中一種最為常見的實現方式。
一、基本定義
懶漢式單例模式也稱為「懶加載」或「延遲初始化」,其主要思想是:只有當第一次請求實例時,才會實例化對象,避免了類在初始化時就實例化對象,從而影響應用的啟動效率。
public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
在上面的代碼中,instance是Singleton類的一個私有靜態變量,用於存儲該類的唯一實例。getInstance()方法是獲取該實例的唯一接口。在每次調用getInstance()方法時,都會先判斷instance是否為空,如果為空,則創建一個新的實例,否則直接返回已存在的實例。
二、線程安全
由於多線程環境下有可能出現競態條件,從而導致兩個線程同時執行到instance == null的判定語句,從而實例化兩個實例,所以懶漢式單例模式的線程安全性較差。
為了避免這種問題,可以採用多種方式對getInstance()方法進行線程安全的設計。下面介紹三種常用的線程安全方案:
1. synchronized關鍵字
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
通過在getInstance()方法上添加synchronized關鍵字,可以將該方法變為同步方法,從而保證了只有一個線程能夠同時進入該方法進行實例化。但是由於每次調用該方法都需要獲得鎖,所以會造成較大的性能損耗。
2. Double Check Lock(雙重校驗鎖)
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }
在Double Check Lock方案中,同樣是通過使用synchronized關鍵字對instance的實例化進行加鎖,但是只有在instance為null時,才會進入synchronized塊進行實例化。這種方式既保證了線程安全性,又可以避免多次獲取鎖造成的性能損耗。
3. 靜態內部類
public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
在靜態內部類方案中,Singleton類中的instance被聲明為私有,並且新建了一個靜態內部類SingletonHolder,在 SingletonHolder類中聲明了一個靜態的、final的、並且是Singleton類型的變量INSTANCE。在getInstance()方法中,直接返回SingletonHolder.INSTANCE,從而保證了線程安全性,同時也可以達到懶加載的效果。
三、序列化與反序列化
在使用單例模式時,有時需要對單例對象進行序列化或反序列化,但是由於單例類的構造函數被私有化,並且getInstance()方法是靜態方法,所以需要對Singleton類進行特殊處理才能保證序列化的正確性。以下是一種序列化與反序列化的方式:
public class Singleton implements Serializable { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } private Object readResolve() throws ObjectStreamException { return instance; } }
在上述代碼中,我們將instance變量聲明為靜態變量,並在定義時就實例化了對象。為了保證反序列化時不會創建新的實例,我們定義了一個readResolve()方法,將反序列化返回的對象直接指向instance,從而保證了單例的正確性。
四、反射
在Java的反射機制中,可以通過調用構造函數的newInstance()方法來創建一個類的實例。對於懶漢式單例模式而言,如果想要通過反射來創建新的實例,則需要對代碼進行特殊的設計。
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { if (instance != null) { throw new IllegalStateException("Already initialized."); } } public static Singleton getInstance() { return instance; } private Object readResolve() throws ObjectStreamException { return instance; } }
在上面的代碼中,我們在Singleton類的私有構造函數中加入了一個判斷語句,判斷instance是否為null。如果instance已經被實例化,則拋出IllegalStateException異常,從而避免通過反射創建新的實例。
五、結論
懶漢式單例模式是一種實現單例模式的常見方式,其主要思想是在需要時才進行實例化。但是由於多線程環境下可能存在線程安全問題,需要進行特殊的處理。此外,序列化與反序列化以及反射都可能對懶漢式單例模式帶來一些問題,需要進行特殊的設計和處理。
原創文章,作者:UNVTW,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/372541.html