面試的時(shí)候忧换,問到許多年輕的Android開發(fā)他所會(huì)的設(shè)計(jì)模式是什么馒胆,基本上都會(huì)提到單例模式热康,但是對(duì)
單例模式也是一知半解,在Android開發(fā)中我們經(jīng)常會(huì)運(yùn)用單例模式载弄,所以我們還是要更了解單例模式才
對(duì)耘拇。
定義:保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)宇攻。
單例模式結(jié)構(gòu)圖:
單例模式有多種寫法各有利弊惫叛,現(xiàn)在
我們來看看各種模式寫法。
1. 餓漢模式
public class Singleton {
private static
Singleton instance = new Singleton();
private Singleton (){
}
public static
Singleton getInstance() {
return instance;
}
}
這種方式在類加載時(shí)就完成了
初始化逞刷,所以類加載較慢嘉涌,但獲取對(duì)象的速度快。 這種方式基于類加載機(jī)制避免了多線程的同步問題夸浅,
但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載仑最,這時(shí)候初始化instance顯然沒有達(dá)
到懶加載的效果。
2. 懶漢模式(線程不安全)
public class Singleton {
private
static Singleton instance;
private Singleton (){
}
public static
Singleton getInstance() {
if (instance == null) {
instance = new
Singleton();
}
return instance;
}
}
懶漢模式申明了一個(gè)靜態(tài)對(duì)象帆喇,
在用戶第一次調(diào)用時(shí)初始化警医,雖然節(jié)約了資源,但第一次加載時(shí)需要實(shí)例化坯钦,反映稍慢一些预皇,而且在多
線程不能正常工作。
3. 懶漢模式(線程安全)
public class Singleton {
private
static Singleton instance;
private Singleton (){
}
public static
synchronized Singleton getInstance() {
if (instance == null) {
instance =
new Singleton();
}
return instance;
}
}
這種寫法能夠在多線程中很
好的工作葫笼,但是每次調(diào)用getInstance方法時(shí)都需要進(jìn)行同步深啤,造成不必要的同步開銷,而且大部分時(shí)候
我們是用不到同步的路星,所以不建議用這種模式溯街。
4. 雙重檢查模式 (DCL)
public class
Singleton {
private volatile static Singleton singleton;
private Singleton
(){
}
public static Singleton getInstance() {
if (instance== null) {
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return singleton;
}
}
這種寫法在getSingleton方法中對(duì)singleton進(jìn)行了兩次判空诱桂,第一次是為了不必要的同步,第
二次是在singleton等于null的情況下才創(chuàng)建實(shí)例呈昔。在這里用到了volatile關(guān)鍵字挥等,不了解volatile關(guān)鍵
字的可以查看Java多線程(三)volatile域這篇文章,在這篇文章我也提到了雙重
檢查模式是正確使用volatile關(guān)鍵字的場景之一堤尾。
在這里使用volatile會(huì)或多或少的影響性能肝劲,但考慮
到程序的正確性,犧牲這點(diǎn)性能還是值得的郭宝。 DCL優(yōu)點(diǎn)是資源利用率高辞槐,第一次執(zhí)行g(shù)etInstance時(shí)單例
對(duì)象才被實(shí)例化,效率高粘室。缺點(diǎn)是第一次加載時(shí)反應(yīng)稍慢一些榄檬,在高并發(fā)環(huán)境下也有一定的缺陷衔统,雖然
發(fā)生的概率很小锦爵。DCL雖然在一定程度解決了資源的消耗和多余的同步沪袭,線程安全等問題枝恋,但是他還是在
某些情況會(huì)出現(xiàn)失效的問題焚碌,也就是DCL失效十电,在《java并發(fā)編程實(shí)踐》一書建議用**靜態(tài)內(nèi)部類單例模
式**來替代DCL。
5. 靜態(tài)內(nèi)部類單例模式
public class Singleton {
private Singleton
(){
}
public static Singleton getInstance(){
return
SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
第一次加載
Singleton類時(shí)并不會(huì)初始化sInstance畏线,只有第一次調(diào)用getInstance方法時(shí)虛擬機(jī)加載
SingletonHolder 并初始化sInstance 蒿叠,這樣不僅能確保線程安全也能保證Singleton類的唯一性市咽,所以
推薦使用靜態(tài)內(nèi)部類單例模式施绎。
6. 枚舉單例
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且在任何情況
下都是單例孤紧,上述講的幾種單例模式實(shí)現(xiàn)中臭猜,有一種情況下他們會(huì)重新創(chuàng)建對(duì)象蔑歌,那就是反序列化园匹,將
一個(gè)單例實(shí)例對(duì)象寫到磁盤再讀回來,從而獲得了一個(gè)實(shí)例。反序列化操作提供了readResolve方法陷揪,這
個(gè)方法可以讓開發(fā)人員控制對(duì)象的反序列化。在上述的幾個(gè)方法示例中如果要杜絕單例對(duì)象被反序列化
是重新生成對(duì)象趁舀,就必須加入如下方法:
private Object readResolve() throws
ObjectStreamException{
return singleton;
}
枚舉單例的優(yōu)點(diǎn)就是簡單巡蘸,但是大部分應(yīng)用開發(fā)很少用
枚舉擂送,可讀性并不是很高搬味,不建議用。
7. 使用容器實(shí)現(xiàn)單例模式
public class
SingletonManager {
private static Map<String, Object> objMap = new
HashMap<String,Object>();
private Singleton() {
}
public static void
registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static ObjectgetService(String
key) {
return objMap.get(key) ;
}
}
用SingletonManager 將多種的單例類統(tǒng)一管理悦析,
在使用時(shí)根據(jù)key獲取對(duì)象對(duì)應(yīng)類型的對(duì)象。這種方式使得我們可以管理多種類型的單例,并且在使用時(shí)
可以通過統(tǒng)一的接口進(jìn)行獲取操作道媚,降低了用戶的使用成本,也對(duì)用戶隱藏了具體實(shí)現(xiàn)锈麸,降低了耦合度
掐隐。
總結(jié)
到這里七中寫法都介紹完了虑省,至于選擇用哪種形式的單例模式,取決于你的項(xiàng)目本身熟丸,是否
是有復(fù)雜的并發(fā)環(huán)境光羞,還是需要控制單例對(duì)象的資源消耗纱兑。