單例設(shè)計(jì)模式是用的最多的設(shè)計(jì)模式,也是最簡單的一中設(shè)計(jì)模式。下面來介紹下幾種實(shí)現(xiàn)單例的方式,以及分析下各自的優(yōu)缺點(diǎn)。
餓漢式
public class CEO {
private static final CEO instance = new CEO();
private CEO() {
}
public static CEO getInstance() {
return instance;
}
}
構(gòu)造函數(shù)私有,利用共有的靜態(tài)函數(shù),對外暴露獲取單例對象的接口。CEO對象在聲明的時(shí)候就初始化了,并且是靜態(tài)的。這樣就保證了對象的唯一性。
懶漢式
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}
與餓漢式的區(qū)別是靜態(tài)對象在第一次調(diào)getInstance()時(shí)進(jìn)行初始化。
synchronized確保getInstance()是個(gè)同步方法,用來確保在多線程情況下單例的唯一性。
優(yōu)點(diǎn):只有在使用時(shí)才被初始化,節(jié)約資源
弊端:每次調(diào)用getInstance()都需要同步,造成不必要的同步開銷
Double Check Lock(DCL)實(shí)現(xiàn)單例
這種模式其實(shí)是對懶漢式的優(yōu)化,將鎖放到內(nèi)部,當(dāng)?shù)谝淮纬跏蓟瘑卫臅r(shí)候才同步:
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
上面進(jìn)行2次判空:
第一次是為了避免不必要的同步
第二次是為了在null的情況下創(chuàng)建實(shí)例
為什么要進(jìn)行第二次判空呢?
我們看下instance = new Singleton()這步會(huì)做什么:
1、給Singleton的實(shí)例分配內(nèi)存;
2、調(diào)用Singleton()的構(gòu)造函數(shù),初始化成員字段;
3、將instance對象指向分配的內(nèi)存空間(這樣instance就不是null了)
但是JDK1.5之前并不能保證第二步和第三步的順序,如果先走的3,那么此時(shí)如果有另外個(gè)線程getInstance()那么它就可以直接獲取單例對象,當(dāng)他使用的時(shí)候就會(huì)出錯(cuò)。
JDK1.5之后只需要將instance第一為private volatile static Singleton instance = null;這樣就行。添加關(guān)鍵字volatile保證instance每次都是從主內(nèi)存中讀取。
靜態(tài)內(nèi)部類單例模式
DCL雖然了資源消耗、多余同步、線程安全等問題,但是在某些并發(fā)情況下會(huì)失效,因此并步推薦使用DCL。
下面這種通過靜態(tài)內(nèi)部類實(shí)現(xiàn)單例的方法是推薦使用的:
public class Singleton {
private Singleton() {
}
/**
* 靜態(tài)內(nèi)部類
*/
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
1、instance只有在調(diào)getInstance()的時(shí)候才初始化
2、通過靜態(tài)內(nèi)部類不僅能確保線程安全,也能保證單例的唯一性
枚舉單例
其實(shí)考慮到唯一性和線程安全性的時(shí)候我就應(yīng)該想到枚舉,下面我們通過枚舉來實(shí)現(xiàn)單例
public enum Singleton {
SINGLETON;
}
先不說性能,至少這是實(shí)現(xiàn)單例最簡單的方法。
枚舉的好處是默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且任何情況下它都是一個(gè)單例。
之前幾種方法都會(huì)存在一個(gè)問題,就是在反序列化時(shí),可能創(chuàng)建一個(gè)新的實(shí)例
總結(jié)
不管哪種形式實(shí)現(xiàn)單例,核心原理就是將構(gòu)造函數(shù)私有化,并通過靜態(tài)方法獲取唯一的實(shí)例。在獲取這個(gè)實(shí)例的過程中要保證線程安全、唯一性等問題