Android開發(fā)中的單例模式應(yīng)用詳解
本文實(shí)例講述了Android開發(fā)中的單例模式應(yīng)用。分享給大家供大家參考,具體如下:
單例模式是應(yīng)用最廣的設(shè)計(jì)模式之一,在應(yīng)用這種模式的時(shí)候,單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在。許多時(shí)候,整個(gè)系統(tǒng)只需要擁有一個(gè)全局對(duì)象,這樣有利于協(xié)調(diào)系統(tǒng)的整體行為。如一個(gè)應(yīng)用中,應(yīng)該只有ImageLoader實(shí)例,這個(gè)ImageLoader實(shí)例中又包含網(wǎng)絡(luò)請(qǐng)求、緩存系統(tǒng)、線程池等,很耗資源,因此沒有理由讓他構(gòu)造多個(gè)實(shí)例。這種不能自由構(gòu)造對(duì)象的情況就是使用單例模式的場(chǎng)景。在Android系統(tǒng)中存在很多這種場(chǎng)景,比如最常用的context.getSystemService()
,BluetoothAdapter.getDefaultAdapter()
等等都是使用的單例模式。下面就列出幾種單例模式的構(gòu)建方式以及各種方式的優(yōu)缺點(diǎn)。
1.懶漢模式
懶漢模式是申明一個(gè)靜態(tài)變量,并且在用戶第一次調(diào)用getInstance時(shí)進(jìn)行初始化,懶漢模式實(shí)現(xiàn)如下:
public class Singleton { private static Singleton sInstance; private Singleton(){ } public static synchronized Singleton getInstance(){ if(sInstance == null){ sInstance = new Singleton(); } return sIntance; } }
getIntance()
方法中添加了synchronized關(guān)鍵字,也就是getInstance是一個(gè)同步方法,這就是上面所說的在多線程情況下保證單例對(duì)象唯一性的手段。但是這樣存在一個(gè)問題,即使sInstance已經(jīng)被初始化,每次調(diào)用getInstance都會(huì)進(jìn)行同步,這樣會(huì)消耗不必要的資源,這也是懶漢單例模式存在的最大問題。懶漢模式的最大優(yōu)點(diǎn)是單例只有在使用的時(shí)候才會(huì)被實(shí)例化,在一定程度上節(jié)約了資源;缺點(diǎn)是第一次加載時(shí)需要及時(shí)初始化,反應(yīng)稍慢,最大的問題是每次調(diào)用getInstance都進(jìn)行同步,造成不必要的同步開銷。一般不建議使用。
2.Double Check Lock(DCL)實(shí)現(xiàn)單例
DCL方式實(shí)現(xiàn)單例模式的優(yōu)點(diǎn)是既能夠在需要的時(shí)候才初始化單例又能保證線程安全,且單例對(duì)象的初始化后調(diào)用getInstance不進(jìn)行同步鎖。實(shí)現(xiàn)如下:
public class Singleton { private static Singleton sInstance; private Singleton(){} public static Singleton getInstance(){ if(sInstance == null){ synchronized(Singleton.class){ if(sInstance == null){ sInstance = new Singleton(); } } } return sInstance; } }
本程序的亮點(diǎn)在于getInstance()
方法中對(duì)sIntance進(jìn)行了兩次非空判斷:第一層主要是為了避免不必要的同步,第二層的判斷則是為了在null的情況下創(chuàng)建實(shí)例。假設(shè)線程A執(zhí)行到sInstance = new Singleton()
的語(yǔ)句,這看起來是原子操作,但并不是,這句代碼會(huì)被編譯成多條匯編指令:給Singleton的實(shí)例分配內(nèi)存,調(diào)用Singleton的構(gòu)造函數(shù),初始化成員字段,將sInstance對(duì)象指向分配的內(nèi)存空間(此時(shí)的sInstance已經(jīng)不是null了)。但是由于Java編譯器允許處理器亂序執(zhí)行,所以上述三個(gè)步驟的執(zhí)行順序無法得到保證。這就會(huì)導(dǎo)致DCL失效,而且這種難以跟蹤重現(xiàn)的錯(cuò)誤會(huì)隱藏很久。在JDK1.5后,只需要將sInstance的定義改成private volatile static Singleton sInstance = null
就可以保證sInstance對(duì)象每次都是從內(nèi)存中讀取,就可以使用DCL的寫法來完成單例模式。當(dāng)然,volatile或多或少會(huì)影響到性能,但考慮到程序的正確性,還是值得的。DCL的優(yōu)點(diǎn)是資源利用率高,第一次執(zhí)行g(shù)etInstance時(shí)單例才會(huì)被實(shí)例化,效率高。缺點(diǎn)是第一次加載時(shí)反應(yīng)稍慢,也由于Java內(nèi)存模型的原因會(huì)偶爾失敗。在高并發(fā)環(huán)境下也有一定的缺陷,雖然發(fā)生概率較小。DCL模式是使用最多的單例實(shí)現(xiàn)方式,它能夠在需要時(shí)才實(shí)例化單例,并且在絕大多數(shù)場(chǎng)景下保證單例對(duì)象的唯一性,除非你的代碼在并發(fā)場(chǎng)景比較復(fù)雜或者低于JDK6的情況下使用,否則這種方式一定能夠滿足要求。
3.靜態(tài)內(nèi)部類單例模式
DCL雖然在一定程度上解決了資源消耗、多余的同步、線程安全等問題,但是它還是在某些情況下出現(xiàn)失效的問題。這個(gè)問題被才會(huì)被稱為DCL雙鎖失效。靜態(tài)內(nèi)部類實(shí)現(xiàn)代碼如下:
public class Singleton{ private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.sInstance; } private static class SingletonHolder { private static final Singleton sInstance = new Singleton(); } }
當(dāng)?shù)谝淮渭虞d的時(shí)候Singleton類時(shí),并不會(huì)初始化sInstance,只有第一次在調(diào)用Singleton的getS方法時(shí)才會(huì)導(dǎo)致sIn被初始化。因此第一次調(diào)用get方法會(huì)導(dǎo)致虛擬機(jī)加載SingletonHolder類,這種方法不但保證能夠=線程安全們也能夠,也能夠保證單例對(duì)象的唯一性,同時(shí)也延遲了單例的實(shí)例化,。
4.枚舉單例模式檔
從Java1.5版本起,單元素枚舉實(shí)現(xiàn)單例模式成為最佳的方法,實(shí)現(xiàn)代碼如下
class Resource{ } public enum SomeThing { INSTANCE; private Resource instance; SomeThing() { instance = new Resource(); } public Resource getInstance() { return instance; } }
上面的類Resource是我們要應(yīng)用單例模式的資源,具體可以表現(xiàn)為網(wǎng)絡(luò)連接,數(shù)據(jù)庫(kù)連接,線程池等等。
獲取資源的方式很簡(jiǎn)單,只要 SomeThing.INSTANCE.getInstance()
即可獲得所要實(shí)例。下面我們來看看單例是如何被保證的:
首先,在枚舉中我們明確了構(gòu)造方法限制為私有,在我們?cè)L問枚舉實(shí)例時(shí)會(huì)執(zhí)行構(gòu)造方法,同時(shí)每個(gè)枚舉實(shí)例都是static final類型的,也就表明只能被實(shí)例化一次。在調(diào)用構(gòu)造方法時(shí),我們的單例被實(shí)例化。
也就是說,因?yàn)閑num中的實(shí)例被保證只會(huì)被實(shí)例化一次,所以我們的INSTANCE也被保證實(shí)例化一次。
可以看到,枚舉實(shí)現(xiàn)單例還是比較簡(jiǎn)單的,除此之外我們?cè)賮砜匆幌翬num這個(gè)類的聲明:
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable
可以看到,枚舉也提供了序列化機(jī)制。某些情況,比如我們要通過網(wǎng)絡(luò)傳輸一個(gè)數(shù)據(jù)庫(kù)連接的句柄,會(huì)提供很多幫助。
在上述的幾種單例模式中,存在一種情況它們會(huì)出現(xiàn)重復(fù)創(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ì)象下被反序列化中重新生成對(duì)象,那么需要加入如下方法:
private Object readResolve() throws ObjectStreamException{ return sInstance; }
也就是在readResolve()
中將sInstance
對(duì)象返回,而不是默認(rèn)的重新生成一個(gè)新的對(duì)象。而對(duì)于枚舉,并不存在這個(gè)問題,因?yàn)榧词狗葱蛄谢膊粫?huì)生成新的實(shí)例。
5.使用容器實(shí)現(xiàn)單例模式
看看這種實(shí)現(xiàn)方式:
public class SingletonManager { private static Map<String,Object> objMap = new HashMap<String,Object>(); private SingletonManager(); public static viud registerServuce(String key,Object instance){ if(!objMap.containsKey(key){ objMao.put(key,instance); } } public static Object getService(String key){ return objMap.get(key); } }
在程序初始化的時(shí)候,將多種單例類型注入到一個(gè)統(tǒng)一的管理類中,在使用時(shí)根據(jù)Key獲取對(duì)象對(duì)應(yīng)類型的獨(dú)享,這種方式使得我們可以管理多種類型的單例,并且在使用時(shí)可以通過統(tǒng)一的接口進(jìn)行獲取操作,降低了用戶的使用成本,也對(duì)用戶隱藏了具體的實(shí)現(xiàn),降低了耦合度。
總結(jié):
不管以哪種方式實(shí)現(xiàn)單例模式,它們的核心原理都是將構(gòu)造函數(shù)私有化,并且通過靜態(tài)方法獲取一個(gè)唯一的實(shí)例,在獲取這個(gè)的過程中必須保證線程安全、防止反序列化導(dǎo)致重新生成實(shí)例對(duì)象等問題。選擇哪種方式取決于項(xiàng)目本身,如是否是復(fù)雜的并發(fā)環(huán)境、JDK版本過低、單例對(duì)象的資源消耗等。
更多關(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ì)有所幫助。
- 如何在Android studio 中使用單例模式
- Android中單例模式的一些坑小結(jié)
- Android編程設(shè)計(jì)模式之單例模式實(shí)例詳解
- Android設(shè)計(jì)模式之單例模式解析
- Android設(shè)計(jì)模式之單例模式詳解
- Android單例模式的幾種方法總結(jié)
- Android 單例模式 Singleton 簡(jiǎn)單實(shí)例設(shè)計(jì)模式解析
- android開發(fā)設(shè)計(jì)模式之——單例模式詳解
- Android設(shè)計(jì)模式系列之單例模式
- android設(shè)計(jì)模式之單例模式詳解
- Android源碼學(xué)習(xí)之單例模式應(yīng)用及優(yōu)點(diǎn)介紹
- Android 單例模式的四種實(shí)現(xiàn)方式
相關(guān)文章
Android實(shí)現(xiàn)果凍滑動(dòng)效果的控件
這篇文章給大家主要介紹了利用Android如何實(shí)現(xiàn)果凍效果滑動(dòng)效果的控件,實(shí)現(xiàn)的效果類似于iOS有阻尼效果的滑動(dòng)控件,一般我們比較親切地稱之為果凍控件,常見的如微信里[我]的那個(gè)面板模塊,即使沒有再多的選項(xiàng),也不會(huì)很生硬的不允許用戶滑動(dòng)。下面來一起看看吧。2016-11-11快速關(guān)閉android studio的自動(dòng)保存功能教程
這篇文章主要介紹了快速關(guān)閉android studio的自動(dòng)保存功能教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04Android開發(fā)手冊(cè)自定義Switch開關(guān)按鈕控件
這篇文章主要為大家介紹了Android開發(fā)手冊(cè)自定義Switch開關(guān)按鈕控件的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06詳解Android原生json和fastjson的簡(jiǎn)單使用
本文主要介紹了Android原生json和fastjson的簡(jiǎn)單使用,具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-01-01Android基于IJKPlayer視頻播放器簡(jiǎn)單封裝設(shè)計(jì)
這篇文章主要介紹了Android基于IJKPlayer視頻播放器簡(jiǎn)單封裝設(shè)計(jì),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06Android EditText隨輸入法一起移動(dòng)并懸浮在輸入法之上的示例代碼
這篇文章主要介紹了Android EditText隨輸入法一起移動(dòng)并懸浮在輸入法之上,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Android?WindowManager深層理解view繪制實(shí)現(xiàn)流程
WindowManager是Android中一個(gè)重要的Service,是全局且唯一的。WindowManager繼承自ViewManager。WindowManager主要用來管理窗口的一些狀態(tài)、屬性、view增加、刪除、更新、窗口順序、消息收集和處理等2022-11-11在啟動(dòng)欄制作android studio啟動(dòng)圖標(biāo)
這篇文章主要介紹了在啟動(dòng)欄制作android studio啟動(dòng)圖標(biāo)的相關(guān)知識(shí),需要的朋友可以參考下2018-03-03