單例模式
單例模式屬于創(chuàng)建型模式桨武,是Gangs of Four Design patterns中其中的一種肋拔。單例模式的實(shí)現(xiàn)有多種方式,很多實(shí)現(xiàn)方式在開(kāi)發(fā)者中也是比較有爭(zhēng)議的呀酸。本文就單例模式的應(yīng)用場(chǎng)景凉蜂,實(shí)現(xiàn)方式,最佳實(shí)踐做一些淺顯的討論性誉。
java singleton
單例模式確保在虛擬機(jī)中只實(shí)例化一個(gè)對(duì)象窿吩,該singleton class要提供一個(gè)對(duì)此實(shí)例的全局訪問(wèn)點(diǎn)。單例模式一般用來(lái)實(shí)現(xiàn)日志错览,緩存纫雁,線程池等。
單例模式uml類(lèi)圖
java singleton patten
java單例模式的實(shí)現(xiàn)由多種方式倾哺,但是所有的實(shí)現(xiàn)方式都遵循以下幾個(gè)概念
- 私有的構(gòu)造函數(shù)轧邪,限制其它類(lèi)對(duì)其實(shí)例化
- 該單例類(lèi)私有的全局靜態(tài)變量
- 訪問(wèn)該單例的共有靜態(tài)方法
下面我們來(lái)討論單例模式的幾種實(shí)現(xiàn)方式以及實(shí)現(xiàn)中的一些設(shè)計(jì)理念
-
饑餓式
public class EagerSingleton { private static EagerSingleton INSTANCE = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance(){ return INSTANCE; } }
饑餓式是在類(lèi)加載的時(shí)候就實(shí)例化了,這樣就有一個(gè)弊端悼粮,就是有時(shí)候我們并沒(méi)有使用該實(shí)例闲勺,但是它卻存在虛擬機(jī)中,如果該實(shí)例引用的資源較少則沒(méi)什么關(guān)系扣猫,反之則顯得比較浪費(fèi)。
- 懶漢式
public class LazySingleton {
private static LazySingleton INSTANCE;
private LazySingleton(){}
public static LazySingleton getInstance(){
if (INSTANCE == null){
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
懶漢式在我們需要使用實(shí)例的時(shí)候它才初始化翘地,這樣就沒(méi)有饑餓式和靜態(tài)塊方式的弊端申尤,但是會(huì)有線程安全的問(wèn)題。
?
- 靜態(tài)塊單例模式實(shí)現(xiàn)方式
public class StaticBlockSingleton {
private static StaticBlockSingleton INSTANCE;
static {
try {
INSTANCE = new StaticBlockSingleton();
} catch (Exception e) {
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
private StaticBlockSingleton(){}
public static StaticBlockSingleton getInstance(){
return INSTANCE;
}
}
靜態(tài)塊方式和饑餓式比較類(lèi)似
- 線程安全的單例模式
public class SynchronizedSingleton {
private static SynchronizedSingleton INSTANCE;
private SynchronizedSingleton(){}
public static synchronized SynchronizedSingleton getInstance(){
if (instance == null){
INSTANCE = new SynchronizedSingleton();
}
return INSTANCE;
}
}
在訪問(wèn)該實(shí)例的靜態(tài)方法前加上synchronized關(guān)鍵字就可以保證線程安全衙耕,但是這樣的方式會(huì)讓計(jì)算機(jī)付出一些額外的代價(jià)昧穿,從而影響了程序性能。為了盡量在不影響性能的情況下實(shí)現(xiàn)線程安全的單例模式橙喘,double checked locking方式就出現(xiàn)了时鸵,在這種方式下,synchronize塊在方法內(nèi)的if條件中使用,且使用了倆個(gè)if條件檢查實(shí)例是否為null饰潜,確保只有一個(gè)實(shí)例被創(chuàng)建初坠。代碼如下
public class DoubleCheckSingleton {
private static DoubleCheckSingleton INSTANCE;
private DoubleCheckSingleton(){}
public static DoubleCheckSingleton getInstance(){
if (instance == null){
synchronized (DoubleCheckSingleton.class){
if (INSTANCE == null){
INSTANCE = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
?
-
Bill Pugh的單例模式實(shí)現(xiàn)方式
在java 5以前,java的內(nèi)存模型在上述方式實(shí)現(xiàn)單例模式的時(shí)候存在許多問(wèn)題彭雾,當(dāng)多個(gè)線程同時(shí)獲取實(shí)例的時(shí)候可能會(huì)失敗碟刺。所以Bill Pugh提出了一種使用內(nèi)部靜態(tài)幫助類(lèi)的方式來(lái)實(shí)現(xiàn)單例模式。
public class BillPughSingleton{ private BillPughSingleton(){} private static class SingletonHelper{ private static final BillPughSingleton INSTANCE = new BillPughSingleton(); } public static BillPughSingleton getInstance(){ return SingletonHelper.INSTANCE; } }
值得注意的是該單例模式的實(shí)例放在私有的內(nèi)部靜態(tài)類(lèi)中薯酝,當(dāng)外部類(lèi)加載到內(nèi)存中的時(shí)候半沽,內(nèi)部的幫助內(nèi)并沒(méi)有加載到內(nèi)存,直到’getInstance()‘方法被調(diào)用吴菠,內(nèi)部幫助類(lèi)才會(huì)加載到內(nèi)存中并創(chuàng)建實(shí)例者填。
這種方式有很多好處,其保證了線程安全做葵,且并沒(méi)有使用synchronized關(guān)鍵字占哟,所以也就不存在額外開(kāi)銷(xiāo)的問(wèn)題。
- Reflection對(duì)單例模式的破壞
反射能破壞以上所有的實(shí)現(xiàn)方式蜂挪,其能確保獲取到不同的實(shí)例重挑。測(cè)試代碼如下
public class ReflectionSingletonTest {
public static void main(String[] args) {
EagerSingleton eagerSingleton = EagerSingleton.getInstance();
System.out.println(eagerSingleton.hashCode());
Constructor [] constructors = EagerSingleton.class.getDeclaredConstructors();
Arrays.asList(constructors).forEach(constructor -> {
constructor.setAccessible(true);
try {
EagerSingleton anotherEagerSingleton = (EagerSingleton) constructor.newInstance();
System.out.println(anotherEagerSingleton.hashCode());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
});
}
}
運(yùn)行結(jié)果如下
356573597
1747585824
-
enum實(shí)現(xiàn)單例模式
為了解決反射場(chǎng)景下的單例模式,Joshua Bloch建議使用enum來(lái)實(shí)現(xiàn)單例模式棠涮,因?yàn)閑num在java中被確保只實(shí)例化一次谬哀。
public enum EnumSinglton { INSTANCE; public void dosomething(){ System.out.println("enum singleton"); } }
?
-
序列化和單例
在分布式系統(tǒng)中,我們需要在單例類(lèi)中實(shí)現(xiàn)序列化接口严肪,這樣我們就能保存實(shí)例的狀態(tài)史煎,并在合適的時(shí)候根據(jù)狀態(tài)恢復(fù)實(shí)例。
public class SerializedSingleton implements Serializable{ private SerializedSingleton(){ } private static class SerializedSingletonHelper{ public static final SerializedSingleton INSTANCE = new SerializedSingleton(); } public static SerializedSingleton getInstance(){ return SerializedSingletonHelper.INSTANCE; } }
測(cè)試代碼
public class SerializedSingletonTest { public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser")); oos.writeObject(SerializedSingleton.getInstance()); System.out.println(SerializedSingleton.getInstance().hashCode()); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser")); SerializedSingleton anotherSingleton = (SerializedSingleton) ois.readObject(); System.out.println(anotherSingleton.hashCode()); } }
測(cè)試結(jié)果
325040804 999966131 Process finished with exit code 0
以上測(cè)試結(jié)果證明了序列化違反了單例模式的原則驳糯,要解決這個(gè)問(wèn)題我們需要在單例類(lèi)中提供一個(gè)readResolve()方法的實(shí)現(xiàn)
protected Object readResolve() { return getInstance(); }
這樣當(dāng)JVM從內(nèi)存中反序列化地"組裝"一個(gè)新對(duì)象時(shí),就會(huì)自動(dòng)調(diào)用這個(gè) readResolve方法來(lái)返回我們指定好的對(duì)象了, 單例規(guī)則也就得到了保證.
我們?cè)谶\(yùn)行測(cè)試代碼 測(cè)試結(jié)果如下
325040804 325040804
?