Android編程設(shè)計(jì)模式之單例模式實(shí)例詳解
本文實(shí)例講述了Android編程設(shè)計(jì)模式之單例模式。分享給大家供大家參考,具體如下:
一、介紹
單例模式是應(yīng)用最廣的模式之一,也可能是很多初級(jí)工程師唯一會(huì)使用的設(shè)計(jì)模式。在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在。許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)全局對(duì)象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。
二、定義
確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。
三、使用場(chǎng)景
確保某個(gè)類有且只有一個(gè)對(duì)象的場(chǎng)景,避免產(chǎn)生多個(gè)對(duì)象消耗過多的資源,或者某種類型的對(duì)象只應(yīng)該有且只有一個(gè)。例如,創(chuàng)建一個(gè)對(duì)象需要消耗的資源過多,如要訪問IO和數(shù)據(jù)庫(kù)等資源,這時(shí)就要考慮使用單例模式。
四、實(shí)現(xiàn)方式
1、餓漢模式
示例代碼:
/**
* 餓漢模式
*/
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
優(yōu)點(diǎn):延遲加載(需要的時(shí)候才去加載)
缺點(diǎn):線程不安全,在多線程中很容易出現(xiàn)不同步的情況,如在數(shù)據(jù)庫(kù)對(duì)象進(jìn)行的頻繁讀寫操作時(shí)。
2、懶漢模式
示例代碼:
/**
* 懶漢模式
*/
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
與餓漢模式相比,getInstance()方法中添加了synchronized關(guān)鍵字,也就是說getInstance是一個(gè)同步方法,這就在多線程的情況下保證單例對(duì)象的唯一性的手段。但是,細(xì)想一下,大家可能會(huì)發(fā)現(xiàn)一個(gè)問題,即使instance已經(jīng)被初始化(第一次調(diào)用的時(shí)候就會(huì)被初始化instance),每次調(diào)用getInstance方法都會(huì)進(jìn)行同步,這樣會(huì)消耗不必要的資源,這也是懶漢模式存在的最大問題。
優(yōu)點(diǎn):解決了線程不安全的問題。
缺點(diǎn):第一次加載時(shí)需要及時(shí)進(jìn)行實(shí)例化,反應(yīng)稍慢,最大問題是每次調(diào)用getInstance都進(jìn)行同步,造成不必要的同步開銷。
補(bǔ)充:在Android源碼中使用的該單例方法有:InputMethodManager,AccessibilityManager等都是使用這種單例模式
3、Double Check Lock(DCL)雙重檢查鎖定
示例代碼:
/**
* 雙重檢查鎖定(DCL)單例模式
*/
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
本程序的亮點(diǎn)自然在getInstance方法上,可以看到getInstance方法中對(duì)instance進(jìn)行了兩次判空:第一層判斷主要是為了避免不必要的同步,第二層的判斷則是為了在null的情況下創(chuàng)建實(shí)例。
假設(shè)線程A執(zhí)行到instance = new Singleton()語句,這里看起來是一句代碼,但實(shí)際上它并不是一個(gè)原子操作,這句代碼最終會(huì)被編譯成多條匯編指令,它大致做了3件事情:
(1)個(gè)Singleton的實(shí)例分配內(nèi)存;
(2)調(diào)用Singleton()的構(gòu)造函數(shù),初始化成員字段;
(3)將instance對(duì)象指向分配的內(nèi)存空間(此時(shí)instance就不是null了)。
但是,由于java編譯器允許處理器亂序執(zhí)行,以及JDK1.5之前JMM(Java Memory Model,即Java內(nèi)存模型)中Cache、寄存器到主內(nèi)存回寫順序的規(guī)定,上面的第二和第三句的順序是無法保證的。也就是說,執(zhí)行順序可能是1-2-3也可能是1-3-2。如果是后者,并且在3執(zhí)行完畢、2未執(zhí)行之前,被切換到線程B上,這時(shí)候instance因?yàn)橐呀?jīng)在線程A內(nèi)執(zhí)行過了第三點(diǎn),instance已經(jīng)是非空了,所有,線程B直接取走了instance,再使用時(shí)就會(huì)出錯(cuò),這就是DCL失效問題,而且這種難以跟蹤難以重現(xiàn)的錯(cuò)誤很可能會(huì)隱藏很久。
在JDK1.5之后,SUN官方已經(jīng)注意到這種問題,調(diào)整了JVM,具體化了volatile關(guān)鍵字,因此,如果JDK是1.5或之后的版本,只需要將instance的定義改成private volatile static Singleton instance就可以保證instance對(duì)象每次都是從主內(nèi)存中讀取,就可以使用DCL的寫法來完成單例模式。當(dāng)然,volatile或多或少也會(huì)影響到性能,但考慮到程序的正確性,犧牲這點(diǎn)性能還是值得的。
優(yōu)點(diǎn):資源利用率高,第一次執(zhí)行g(shù)etInstance時(shí)單例對(duì)象才會(huì)被實(shí)例化,效率高。在并發(fā)量不多,安全性不高的情況下或許能很完美運(yùn)行單例模式
缺點(diǎn):第一次加載時(shí)反應(yīng)稍慢,也由于Java內(nèi)存模型的原因偶爾會(huì)失敗。在高并發(fā)環(huán)境下也有一定的缺陷,雖然發(fā)生概率很小。
補(bǔ)充:在android圖像開源項(xiàng)目Android-Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader)中使用的是這種方式。
DCL模式是使用最多的單例實(shí)現(xiàn)方式,它能夠在需要時(shí)才實(shí)例化單例對(duì)象,并且能夠在絕大多數(shù)場(chǎng)景下保證單例對(duì)象的唯一性,除非你的代碼在并發(fā)場(chǎng)景比較復(fù)雜或者低于JDK6版本下使用,否則,這種方式一般能夠滿足需要。
4、靜態(tài)內(nèi)部類單例模式
DCL雖然在一定程度上解決了資源消耗、多余的同步、線程安全等問題,但是,它還是在某些情況下出現(xiàn)失效的問題。這個(gè)問題被稱為雙重檢查鎖定(DCL)失效,在《Java并發(fā)編程實(shí)踐》一書的最后談到了這個(gè)問題,并指出這種“優(yōu)化”是丑陋的,不贊成使用。而建議使用如下的代碼替代:
示例代碼:
/**
* 靜態(tài)內(nèi)部類單例模式
*/
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
/**
* 靜態(tài)內(nèi)部類
* 延遲加載,減少內(nèi)存開銷
*/
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
}
當(dāng)?shù)谝淮渭虞dSingleton類時(shí)并不會(huì)初始化instance,只有在第一次調(diào)用Singleton的getInstance方法時(shí)才會(huì)導(dǎo)致instance被初始化。因此,第一次調(diào)用getInstance方法會(huì)導(dǎo)致虛擬機(jī)加載SingletonHolder類,這種方式不僅能夠確保線程安全,也能夠保證單例對(duì)象的唯一性,同時(shí)也延遲了單例的實(shí)例化,所以這是推薦使用的單例模式實(shí)現(xiàn)方式。
優(yōu)點(diǎn):延遲加載,線程安全(java中class加載時(shí)互斥的),也減少了內(nèi)存消耗
5、枚舉單例
前面講解了一些單例模式實(shí)現(xiàn)方式,但是,這些實(shí)現(xiàn)方式不是稍顯麻煩就是會(huì)在某些情況下出現(xiàn)問題。
示例代碼:
/**
* 枚舉單例模式
*/
public enum Singleton {
/**
* 1.從Java1.5開始支持;
* 2.無償提供序列化機(jī)制;
* 3.絕對(duì)防止多次實(shí)例化,即使在面對(duì)復(fù)雜的序列化或者反射攻擊的時(shí)候;
*/
instance;
private String others;
Singleton() {
}
public String getOthers() {
return others;
}
public void setOthers(String others) {
this.others = others;
}
}
寫法簡(jiǎn)單是枚舉單例最大的優(yōu)點(diǎn),枚舉在Java中與普通的類是一樣的,不僅能夠有字段,還能有自己的方法。最重要的是默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且在任何情況下它都是一個(gè)單例。
為什么這么說呢?在上述的幾種單例模式實(shí)現(xiàn)中,在一個(gè)情況下它們會(huì)出現(xiàn)重新創(chuàng)建對(duì)象的情況,那就是反序列化。
通過序列化可以將一個(gè)單例的實(shí)例對(duì)象寫到磁盤,然后在讀回來,從而有效的獲得一個(gè)實(shí)例。即使構(gòu)造函數(shù)是私有的,反序列化時(shí)依然可以通過特殊的途徑去創(chuàng)建類的一個(gè)新的實(shí)例,相當(dāng)于調(diào)用該類的構(gòu)造函數(shù)。反序列化操作提供了一個(gè)特別的鉤子函數(shù),類中具有一個(gè)私有的、被實(shí)例化的方法readResolve(),這個(gè)方法可以讓開發(fā)人員控制對(duì)象的反序列化。例如,上述幾個(gè)示例中如果要杜絕單例對(duì)象在被反序列化時(shí)重新生成對(duì)象,那么必須加入如下方法:
private Object readResolve() throws ObjectStreamException {
return instance;
}
也就是在readResolve方法中將instance對(duì)象返回,而不是默認(rèn)的重新生成一個(gè)新的對(duì)象。而對(duì)于枚舉,并不存在這個(gè)問題,因?yàn)榧词狗葱蛄谢膊粫?huì)重新生成新的實(shí)例。
優(yōu)點(diǎn):無償提供序列化機(jī)制,絕對(duì)防止多次實(shí)例化,即使在面對(duì)復(fù)雜的序列化或者反射攻擊的時(shí)候。
缺點(diǎn):從Java1.5開始支持。
上面主要講了單例模式5種創(chuàng)建方法,大家可以根據(jù)其優(yōu)缺點(diǎn)進(jìn)行個(gè)人實(shí)際項(xiàng)目中的使用。
更多關(guān)于Android相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Android開發(fā)入門與進(jìn)階教程》、《Android調(diào)試技巧與常見問題解決方法匯總》、《Android基本組件用法總結(jié)》、《Android視圖View技巧總結(jié)》、《Android布局layout技巧總結(jié)》及《Android控件用法總結(jié)》
希望本文所述對(duì)大家Android程序設(shè)計(jì)有所幫助。
相關(guān)文章
Android實(shí)現(xiàn)閱讀進(jìn)度記憶功能
這篇文章主要介紹了Android實(shí)現(xiàn)閱讀進(jìn)度記憶功能,Android控件WebView實(shí)現(xiàn)保存閱讀進(jìn)度,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Android 一鍵清理、內(nèi)存清理功能實(shí)現(xiàn)
這篇文章主要介紹了Android 一鍵清理、內(nèi)存清理功能實(shí)現(xiàn),非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-01-01
Fedora14下android開發(fā): eclipse與ibus確有沖突的問題分析
本篇文章是對(duì)Fedora14下android開發(fā),eclipse與ibus確有沖突的問題進(jìn)行了分析介紹,需要的朋友參考下2013-05-05
Android中webView加載H5綁定cookie實(shí)例
這篇文章主要介紹了Android中webView加載H5綁定cookie實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android registerForActivityResult動(dòng)態(tài)申請(qǐng)權(quán)限案例詳解
這篇文章主要介紹了Android registerForActivityResult動(dòng)態(tài)申請(qǐng)權(quán)限案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
Android 基礎(chǔ)入門教程——開發(fā)環(huán)境搭建
這篇文章主要介紹了Android 如何搭建開發(fā)環(huán)境,文中講解非常細(xì)致,幫助大家開始學(xué)習(xí)Android,想要學(xué)習(xí)Android的朋友可以了解下2020-06-06
Android編程實(shí)現(xiàn)仿易信精美彈出框效果【附demo源碼下載】
這篇文章主要介紹了Android編程實(shí)現(xiàn)仿易信精美彈出框效果,涉及Android窗口及動(dòng)畫操作相關(guān)技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2017-01-01
Android開發(fā)之Activity全透明漸變切換方法
下面小編就為大家分享一篇Android開發(fā)之Activity全透明漸變切換方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Android Camera是否支持變焦的判斷方法總結(jié)
這篇文章主要介紹了Android Camera是否支持變焦的判斷方法總結(jié),本文總結(jié)了調(diào)節(jié)攝像頭焦距編程中遇到的一些問題和解決方法,需要的朋友可以參考下2015-04-04

