單例的定義
確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并且向整個(gè)系統(tǒng)提供整個(gè)實(shí)例紊撕。
使用場(chǎng)景
確保一個(gè)類有且只有一個(gè)對(duì)象,避免產(chǎn)生多個(gè)對(duì)象消耗資源憨募,比如訪問IO和數(shù)據(jù)庫(kù)等資源的時(shí)候就需要使用單例
實(shí)現(xiàn)方式
最簡(jiǎn)單的實(shí)現(xiàn)紧索,線程不安全
保證只有一個(gè)對(duì)象,那么構(gòu)造方法肯定得是私有的菜谣,通過(guò)一個(gè)靜態(tài)方法獲取對(duì)象珠漂,當(dāng)對(duì)象為空的時(shí)候初始化對(duì)象晚缩,否則直接返回,需要用到的時(shí)候再去初始化媳危,屬于懶加載模式荞彼。
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
缺點(diǎn)就是當(dāng)多線程調(diào)用getInstance
的時(shí)候可能會(huì)初始化多個(gè)對(duì)象,所以這個(gè)方法不可取
懶漢式待笑,線程安全
那么可以加上有個(gè)同步來(lái)試試
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
其實(shí)這樣是OK的鸣皂,當(dāng)執(zhí)行方法的時(shí)候,在同一時(shí)刻只能有一個(gè)線程得到執(zhí)行暮蹂,另一個(gè)線程受阻塞寞缝,必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊;但是有個(gè)小問題仰泻,這樣效率比較低荆陆;所以可以稍微優(yōu)化一下,把同步放到了判斷為空以后
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
這個(gè)樣子看起來(lái)是可以的集侯,但是還是有一個(gè)小問題被啼,不容易被發(fā)現(xiàn);就是如果一個(gè)線程執(zhí)行到singleton = new Singleton()
這一步的時(shí)候(不屬于原子操作)棠枉,這句代碼包括了三步浓体,
- 給
singleton
分配內(nèi)存,- 調(diào)用 Singleton 的構(gòu)造函數(shù)來(lái)初始化成員變量术健,
- 將instance對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 instance 就為非 null 了)
在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化汹碱,上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2荞估,所以如果直接執(zhí)行了1-3-2咳促,這時(shí)候剛好另一個(gè)線程執(zhí)行進(jìn)來(lái),執(zhí)行if (singleton == null)
代碼勘伺,判斷不為空然后拿著對(duì)象去使用跪腹,但其實(shí)變量還沒有初始化,程序就會(huì)出錯(cuò)飞醉;
雙重檢驗(yàn)鎖模式
我們可以將singleton
變量聲明成volatile
冲茸,它有一個(gè)特性就是禁止指令重排序優(yōu)化,這樣就沒有問題了缅帘;
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
餓漢式 static final field
但是有沒有發(fā)現(xiàn)上面這種方法寫起來(lái)有點(diǎn)麻煩轴术;所以簡(jiǎn)單點(diǎn),可以直接使用靜態(tài)變量
public class Singleton {
private final static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singleton;
}
}
這種寫法其實(shí)是OK的钦无,可以使用的逗栽,缺點(diǎn)在于不是一種懶加載模式,加載類后就被初始化失暂,即使可能用不到彼宠;而且在有些場(chǎng)景鳄虱,比如需要傳入?yún)?shù)時(shí)候,就不適用了
靜態(tài)內(nèi)部類 static nested class
我比較喜歡這種方法凭峡,感覺是比較好的拙已,寫起來(lái)也比較方便,這種方法也是《Effective Java》上所推薦的
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
枚舉 Enum
枚舉也是一種很好的方法摧冀,很簡(jiǎn)單倍踪,而且還能防止反序列化導(dǎo)致重新創(chuàng)建新的對(duì)象;但是好像一般也沒什么人用按价,我覺得還OK
public enum Singleton {
SINGLETON;
public static void main(String[] args){
Singleton singleton=Singleton.SINGLETON;
}
}
使用容器實(shí)現(xiàn)單例模式
可以在程序初始化的時(shí)候?qū)⒏鞣N單例類型注入到一個(gè)統(tǒng)一的管理類中惭适,根據(jù)key值來(lái)獲取相應(yīng)的對(duì)象,這也是一種方法
public class SingletonManager {
private static Map<String, Object> objectMap = new HashMap<>();
private SingletonManager() {
}
/**
* 注入單例
*
* @param key
* @param instance
*/
public static void registerService(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
}
/**
* 獲取單例
*
* @param key
* @return
*/
public static Object getService(String key) {
return objectMap.get(key);
}
}
總結(jié)
大概也就這幾種常見的單例寫法楼镐,其實(shí)就是為了確保一個(gè)類有且只有一個(gè)對(duì)象癞志,自己覺得哪種合適,哪種方便就使用哪種好了框产;