設(shè)計(jì)模式之單例模式詳解
單例模式寫(xiě)法大全,也許有你不知道的寫(xiě)法
導(dǎo)航
- 引言
- 什么是單例?
- 單例模式作用
- 單例模式的實(shí)現(xiàn)方法
引言
單例模式想必是大家接觸的比較多的一種模式了,就算沒(méi)用過(guò)但是肯定聽(tīng)過(guò)他的鼎鼎大名了。在我初入編程界時(shí)聽(tīng)到最多的就是單例模式侵续,工廠模式,觀察者模式了憨闰。特別是觀察者模式在Android開(kāi)發(fā)中幾乎是隨處可見(jiàn)状蜗,不過(guò)今天我們先來(lái)學(xué)習(xí)一個(gè)看似簡(jiǎn)單很多的單例模式。
什么是單例模式鹉动?
單例模式確保某一個(gè)類(lèi)只有一個(gè)實(shí)例轧坎。
單例模式有什么用?
為什么要確保一個(gè)類(lèi)只有一個(gè)實(shí)例训裆?有什么時(shí)候才需要用到單例模式呢眶根?聽(tīng)起來(lái)一個(gè)類(lèi)只有一個(gè)實(shí)例好像沒(méi)什么用呢蜀铲!
那我們來(lái)舉個(gè)例子。比如我們的APP中有一個(gè)類(lèi)用來(lái)保存運(yùn)行時(shí)全局的一些狀態(tài)信息属百,如果這個(gè)類(lèi)實(shí)現(xiàn)不是單例的记劝,那么App里面的組件能夠隨意的生成多個(gè)類(lèi)用來(lái)保存自己的狀態(tài),等于大家各玩各的族扰,那這個(gè)全局的狀態(tài)信息就成了笑話(huà)了厌丑。而如果把這個(gè)類(lèi)實(shí)現(xiàn)成單例的,那么不管App的哪個(gè)組件獲取到的都是同一個(gè)對(duì)象(比如Application類(lèi)渔呵,除了多進(jìn)程的情況下)怒竿。
怎么實(shí)現(xiàn)單例模式?
單例模式的定義和功能都是比較簡(jiǎn)單清楚的東西扩氢,那么到底怎么實(shí)現(xiàn)這個(gè)模式呢耕驰?
1.餓漢式
可能有的小伙伴們會(huì)想到利用Java的靜態(tài)域初始化機(jī)制來(lái)實(shí)現(xiàn)
public class SimpleSingleton {
private static SimpleSingleton instance=new SimpleSingleton();
/**
* 構(gòu)造方法私有化,幾乎所有的單例模式實(shí)現(xiàn)都會(huì)將構(gòu)造方法私有化
*/
private SimpleSingleton() {
}
public static SimpleSingleton getInstance(){
return instance;
}
}
??這種寫(xiě)法很簡(jiǎn)單录豺,先把構(gòu)造函數(shù)設(shè)為private的(幾乎大部分的單例寫(xiě)法都會(huì)這么做)朦肘。然后在類(lèi)中設(shè)置一個(gè)靜態(tài)的字段,并調(diào)用構(gòu)造函數(shù)双饥。這樣jvm在加載這個(gè)類(lèi)的時(shí)候就會(huì)自動(dòng)初始化這個(gè)類(lèi)媒抠,接著每次需要使用的時(shí)候都調(diào)用getInstance方法獲得類(lèi)實(shí)例。而java的語(yǔ)法規(guī)則保證new SimpleSingleton()自會(huì)調(diào)用一次咏花。
??使用這種方式實(shí)現(xiàn)的單例是線(xiàn)程安全的(這一點(diǎn)是由jvm保證的)趴生,并且在類(lèi)加載的時(shí)候就已經(jīng)生成了一個(gè)實(shí)例,當(dāng)調(diào)用的時(shí)候get獲取這個(gè)實(shí)例是就非郴韬玻快苍匆。
凡事都有優(yōu)缺點(diǎn),餓漢式單例也不例外矩父。他的一個(gè)很明顯的缺點(diǎn)就是在性能上锉桑。jvm會(huì)在加載類(lèi)的時(shí)候直接初始化實(shí)例,而如果這個(gè)類(lèi)的實(shí)例在應(yīng)用中使用頻率并不高窍株,有的時(shí)候整個(gè)App從被用戶(hù)打開(kāi)到結(jié)束都不會(huì)使用一次這個(gè)類(lèi)實(shí)例,那么這個(gè)初始化的操作就是完全浪費(fèi)了攻柠。
為了解決這個(gè)問(wèn)題球订,我們想了一種新的實(shí)現(xiàn)方式。
2.懶漢式
public class ServiceNotThreadSafe {
public static ServiceNotThreadSafe INSTANCE = null;
/**
* 必備操作
*/
private ServiceNotThreadSafe() {
}
/**
* @return instance
*/
public static ServiceNotThreadSafe getInstance() {
if (INSTANCE == null) {
INSTANCE = new ServiceNotThreadSafe();
}
return INSTANCE;
}
}
懶漢式的寫(xiě)法有一個(gè)懶加載的效果瑰钮,只有當(dāng)?shù)谝淮握{(diào)用getInstance方法時(shí)才會(huì)去實(shí)例化一個(gè)對(duì)象冒滩。可能細(xì)心的小伙伴已經(jīng)注意到這個(gè)很直白的類(lèi)名了NotThreadSafe浪谴。沒(méi)錯(cuò)這種寫(xiě)法在單線(xiàn)程中沒(méi)有任何問(wèn)題开睡,但是在并發(fā)程序中就無(wú)法保證 類(lèi)實(shí)例只有一個(gè)的情況了因苹。
為了解決上面的問(wèn)題,我們相出了一個(gè)新的寫(xiě)法
3.懶漢式 —— 單鎖定法
public class ServiceThreadSafe {
public static ServiceThreadSafe instance;
/**
* 還是常規(guī)操作的私有構(gòu)造函數(shù)
*/
private ServiceThreadSafe() {
}
public static synchronized ServiceThreadSafe getInstance(){
if (instance==null){
instance=new ServiceThreadSafe();
}
return instance;
}
}
這種寫(xiě)法沒(méi)什么好說(shuō)的篇恒,只是在getInstance方法上加了一個(gè)內(nèi)置同步鎖扶檐,從而保證了線(xiàn)程安全。但是也因此引入了一個(gè)新的問(wèn)題 —— 同步鎖范圍太大胁艰,影響并發(fā)性能(在getInstance方法并不是頻繁調(diào)用下問(wèn)題不大)款筑。
為了解決這個(gè)問(wèn)題,聰明的程序員們想到了另一種寫(xiě)法腾么。
4.雙重鎖定法
public class ServiceDoubleCheck {
/**
* 注意這里加了 volatile 修飾符奈梳,用來(lái)保證內(nèi)存可見(jiàn)性(限制指令重排序)。
* 具體各位小伙伴可以Google一下解虱。
* 如果你沒(méi)加這個(gè)修飾符的話(huà)攘须,那么具體結(jié)果只能看編譯器,jvm和cpu的心情了O(∩_∩)O~~
*/
public static volatile ServiceDoubleCheck instance = null;
/**
* 大家都懂得操作
*/
private ServiceDoubleCheck() {
}
/**
* 理論上只要第一次的時(shí)候才會(huì)完全走完整個(gè)方法殴泰,之后進(jìn)入這個(gè)方法時(shí)instance==null都不成立
* 而不用在進(jìn)入內(nèi)部的同步代碼塊阻课,帶來(lái)新能上的優(yōu)勢(shì)
* @return
*/
public static ServiceDoubleCheck getInstance() {
if (instance == null) {
synchronized (ServiceDoubleCheck.class) {
if (instance == null) {
instance = new ServiceDoubleCheck();
}
}
}
return instance;
}
}
優(yōu)點(diǎn):
- 資源利用率高,懶加載的形式艰匙,不使用就不會(huì)實(shí)例化
- 線(xiàn)程安全
缺點(diǎn): - 寫(xiě)法略微繁瑣
- 第一次加載時(shí)速度不快
5.懶漢式靜態(tài)內(nèi)部類(lèi)寫(xiě)法
public class ServiceInner {
/**
* 實(shí)現(xiàn)一個(gè)靜態(tài)內(nèi)部類(lèi)
*/
private static class Instance{
private static ServiceInner instance=new ServiceInner();
}
public static ServiceInner getInstance(){
return Instance.instance;
}
private ServiceInner() {
}
}
優(yōu)點(diǎn):
- 線(xiàn)程安全
- 懶加載形式限煞,資源利用率高
缺點(diǎn): - 第一次加載速度不快
6.枚舉實(shí)現(xiàn) —— 一個(gè)《effective java》 作者都推薦的方法
public class Resources {
public enum ResourcesInstance {
INSTANCE;
private Resources instance;
ResourcesInstance() {
this.instance = new Resources();
}
public Resources getInstance() {
return instance;
}
}
}
之前介紹過(guò)的哪些單例實(shí)現(xiàn)都有一個(gè)問(wèn)題,就是不能保證序列化生成另一個(gè)實(shí)例员凝。比如先序列化寫(xiě)入到文件署驻,然后再?gòu)奈募x取反序列化回來(lái),這樣子我們就會(huì)得到兩個(gè)實(shí)例健霹,這就違背了單例的原則旺上。而用枚舉實(shí)現(xiàn)能解決這個(gè)問(wèn)題。
總結(jié)
基本上單例的實(shí)現(xiàn)方法都介紹完了糖埋,一般實(shí)際應(yīng)用中如果對(duì)象的實(shí)例化并不是很耗費(fèi)資源的話(huà)使用最簡(jiǎn)單的餓漢法就行了宣吱。如果需要對(duì)象懶加載則可以選用雙重鎖定法(如果不需要考慮線(xiàn)程安全的話(huà)可以使用簡(jiǎn)單的懶漢式)。而要在序列化的過(guò)程中保證單例的話(huà)就要使用枚舉的方法來(lái)實(shí)現(xiàn)了瞳别。