摘要:即便如此,出于效率考慮,推薦使用雙重校驗(yàn)鎖和靜態(tài)內(nèi)部類(lèi)單例模式。
概述
單例模式是應(yīng)用最廣的模式之一,在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類(lèi)必須保證只有一個(gè)實(shí)例存在。許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)全局對(duì)象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。如在一個(gè)應(yīng)用中,應(yīng)該只有一個(gè)ImageLoader實(shí)例,這個(gè)ImageLoader中又含有線程池、緩存系統(tǒng)、網(wǎng)絡(luò)請(qǐng)求等,很消耗資源。因此不應(yīng)該讓它構(gòu)造多個(gè)實(shí)例。這樣不能自由構(gòu)造對(duì)象的情況,就是單例模式的使用場(chǎng)景。
定義確保一個(gè)類(lèi)只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。
使用場(chǎng)景確保某個(gè)類(lèi)有且只要一個(gè)對(duì)象的場(chǎng)景,避免產(chǎn)生多個(gè)對(duì)象消耗過(guò)多的資源,或者某種類(lèi)型的對(duì)象只應(yīng)該有且只有一個(gè)。例如,創(chuàng)建一個(gè)對(duì)象需要消耗的資源過(guò)多,如要訪問(wèn)IO和數(shù)據(jù)庫(kù)等資源,這時(shí)就要考慮使用單例模式。
UML類(lèi)圖單例模式的UML類(lèi)圖如下:
角色介紹:
Client:高層客戶(hù)端
Singleton:?jiǎn)卫?lèi)
實(shí)現(xiàn)單例模式主要有以下幾個(gè)關(guān)鍵點(diǎn):
構(gòu)造函數(shù)不對(duì)外開(kāi)放,一般為private;
通過(guò)一個(gè)靜態(tài)方法或者枚舉返回單例類(lèi)對(duì)象;
確保單例類(lèi)的對(duì)象有且只有一個(gè),尤其是在多線程環(huán)境下;
確保單例類(lèi)對(duì)象在反序列化時(shí)不會(huì)重新構(gòu)建對(duì)象;
單例模式中實(shí)現(xiàn)比較困難的是在多線程環(huán)境下構(gòu)造單例類(lèi)的對(duì)象有且只有一個(gè)。
簡(jiǎn)單示例單例模式在設(shè)計(jì)模式中是結(jié)構(gòu)比較簡(jiǎn)單的,只有一個(gè)單例類(lèi),沒(méi)有其他層次結(jié)構(gòu)和抽象。該模式需要確保該類(lèi)只能生成一個(gè)對(duì)象,通常是該類(lèi)需要消耗較多的資源或者沒(méi)有對(duì)個(gè)實(shí)例的情況。例如一個(gè)公司只有一個(gè)CEO、一個(gè)應(yīng)用只有一個(gè)Application對(duì)象等。
下面以公司里的CEO為例來(lái)簡(jiǎn)單演示一下,一個(gè)公司可以有多個(gè)VP、無(wú)數(shù)個(gè)員工,但只有一個(gè)CEO,代碼如下:
/** * * 普通員工 * */ public class Staff { public void work() { //干活 } } //副總裁 public class VP extends Staff { @Override public void work() { // 管理下面的經(jīng)理 } } //CEO,餓漢式單例 public class CEO extends Staff { private static final CEO mCEO = new CEO(); private CEO() { } //公有的靜態(tài)函數(shù),對(duì)外暴露獲取單例對(duì)象的接口 public static CEO getCeo() { return mCEO; } @Override public void work() { // 管理VP } } //公司類(lèi) public class Company { private ListmStaffs = new ArrayList (); public void addStaff(Staff staff) { mStaffs.add(staff); } public void showStaffs() { for(Staff staff : mStaffs) { System.out.println("Obj: " + staff.toString()); } } } public static void main(String[] args) { // TODO Auto-generated method stub Company company = new Company(); //CEO對(duì)象只能通過(guò)getCeo獲取 Staff ceo1 = CEO.getCeo(); Staff ceo2 = CEO.getCeo(); company.addStaff(ceo1); company.addStaff(ceo2); Staff vp1 = new VP(); Staff vp2 = new VP(); company.addStaff(vp1); company.addStaff(vp2); Staff staff1 = new Staff(); Staff staff2 = new Staff(); company.addStaff(staff1); company.addStaff(staff2); company.showStaffs(); }
運(yùn)行輸出結(jié)果如下:
Obj: com.liuguoquan.design.single.CEO@15db9742 Obj: com.liuguoquan.design.single.CEO@15db9742 Obj: com.liuguoquan.design.single.VP@6d06d69c Obj: com.liuguoquan.design.single.VP@7852e922 Obj: com.liuguoquan.design.single.Staff@4e25154f Obj: com.liuguoquan.design.single.Staff@70dea4e
從上面代碼可以看出,CEO類(lèi)不能通過(guò)new的形式構(gòu)造函數(shù),只能通過(guò)CEO.getCeo()方法來(lái)獲取,而這個(gè)CEO對(duì)象是靜態(tài)對(duì)象,并且在聲明的時(shí)候就已經(jīng)初始化,這就保證類(lèi)CEO對(duì)象的唯一性。
從輸出結(jié)果中可以看出,CEO兩次輸出的CEO對(duì)象的地址都一樣,說(shuō)明是同一個(gè)CEO對(duì)象;而VP、Staff等類(lèi)型的對(duì)象都是不同的。
實(shí)現(xiàn)方式 餓漢式餓漢式模式是在聲明靜態(tài)對(duì)象時(shí)就已經(jīng)初始化,這種方式簡(jiǎn)單粗暴,如果單例對(duì)象初始化非??欤艺加脙?nèi)存小的時(shí)候這種方式是比較適合的,可以直接在應(yīng)用啟動(dòng)時(shí)加載初始化。實(shí)現(xiàn)如下:
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }懶漢式
懶漢模式是聲明一個(gè)靜態(tài)對(duì)象,并且在用戶(hù)第一次調(diào)用getInstance時(shí)進(jìn)行初始化。
public class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } return instance; } }
getInstance方法中添加了Synchronized關(guān)鍵字,也就是同步類(lèi)synchronized關(guān)鍵字包含的代碼塊,這就是上面所說(shuō)的在多線程中保證單例對(duì)象唯一性的手段。但是仍存在一個(gè)問(wèn)題,即使instance已經(jīng)初始化,每次調(diào)用getInstance方法都會(huì)進(jìn)行同步,這樣會(huì)消耗不必要的資源,這也是懶漢式存在的最大問(wèn)題。
懶漢單例模式的優(yōu)點(diǎn)是只有在使用時(shí)才會(huì)被實(shí)例化,在一定程度上節(jié)約了資源,缺點(diǎn)是第一次加載時(shí)需要及時(shí)進(jìn)行實(shí)例化,反應(yīng)稍慢,最大問(wèn)題是每次調(diào)用geInstance都進(jìn)行同步,造成不必要的同步開(kāi)銷(xiāo),這樣模式一般不建議使用。
Double CheckLock(雙重校驗(yàn)鎖)DCL方式的優(yōu)點(diǎn)是既能夠在需要時(shí)才初始化單例,又能夠保證線程的安全,且單例對(duì)象初始化后調(diào)用getInstance不獲取同步鎖。
public class Singleton { //private static volatile Singleton instance = null; private static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { //如果已經(jīng)初始化,不需要每次獲取同步鎖 if(instance == null) { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } return instance; } }
可以看到getInstance方法對(duì)instance進(jìn)行了兩次判空:第一層判斷主要是為了避免不必要的同步,第二層判斷主要?jiǎng)t是為了在null的情況下創(chuàng)建實(shí)例。下面,我們來(lái)分析一下:
假設(shè)線程A執(zhí)行到instance=new Singleton()語(yǔ)句,這里看起來(lái)是一句代碼,但實(shí)際上它并不是一個(gè)原子操作,這局代碼最終會(huì)被編譯成多條匯編指令,它大致做了3件事情:
給Singleton的實(shí)例分配內(nèi)存
調(diào)用Singleton()的 構(gòu)造函數(shù),初始化字段成員
將instance對(duì)象執(zhí)行分配的內(nèi)存空間(此時(shí)instance就不是null了)
但是,由于Java編譯器運(yùn)行處理器亂序執(zhí)行,以及jdk1.5之前Java內(nèi)存模型中Cache、寄存器到主內(nèi)存會(huì)寫(xiě)順序的規(guī)定,上面的第二和第三的順序是無(wú)法保證的。也就是說(shuō),執(zhí)行順序可能是1-2-3也可能是1-3-2.如果是后者,并且在3執(zhí)行完畢、2未執(zhí)行之前,被切換到線程B上,這時(shí)候instance因?yàn)橐呀?jīng)在線程A內(nèi)執(zhí)行3了,instance已經(jīng)是非null,所有線程B直接取走instance,再使用時(shí)就會(huì)出錯(cuò),這就是DCL失效問(wèn)題,而且這種難以跟蹤難以重現(xiàn)的問(wèn)題很可能會(huì)隱藏很久。
在jdk1.5之后,官方已經(jīng)注意到這種問(wèn)題,調(diào)整了JMM、具體化了volatile關(guān)鍵字,因此,如果是1.5或之后的版本,只需要將instance的定義改成private static volatile Singleton instance = null;就可以保證instance對(duì)象每次都是從主內(nèi)存中讀取,就可以使用DCL的寫(xiě)法來(lái)完成單例模式。當(dāng)然,volatile多少會(huì)影響到性能,但考慮到程序的正確性,犧牲這點(diǎn)性能還是值得的。
DCL的優(yōu)點(diǎn):資源利用率高,第一次執(zhí)行g(shù)etInstance時(shí)單例對(duì)象才會(huì)被實(shí)例化,效率高。
缺點(diǎn):第一次加載稍慢,也由于Java內(nèi)存模型的原因偶爾會(huì)失敗。在高并發(fā)的環(huán)境下也有一定的缺陷,雖然概率發(fā)生很小。
靜態(tài)內(nèi)部類(lèi)單例模式DCL模式是使用最多的單例實(shí)現(xiàn)模式,它能夠在需要時(shí)才實(shí)例化單例對(duì)象,并且能夠在絕大多數(shù)場(chǎng)景下保證單例對(duì)象的唯一性,除非你的代碼在并發(fā)場(chǎng)景比較復(fù)雜或者低于jdk1.6版本下使用,否則這種方式一般能夠滿(mǎn)足需求。
在《Java并發(fā)編程實(shí)戰(zhàn)》中談到不贊成使用DCL的優(yōu)化方式,而建議使用如下代碼替代:
public class Singleton { private Singleton() {} public static Singleton getInstance() { return SingletonHolder.instance; } //靜態(tài)內(nèi)部類(lèi) private static class SingletonHolder { private static final Singleton instance = new Singleton(); } }
當(dāng)?shù)谝淮渭虞dSingleton類(lèi)時(shí)并不會(huì)初始化instance,只有第一次調(diào)用Singleton的getInstance方法時(shí)才會(huì)導(dǎo)致instance被初始化。因此,第一次調(diào)用getInstance方法會(huì)導(dǎo)致虛擬機(jī)加載SingletonHolder類(lèi),這種方式不僅能夠確保線程安全,也能夠保證單例對(duì)象的唯一性,同時(shí)也延遲了單例的實(shí)例化,所以這是推薦使用的單例模式實(shí)現(xiàn)方式。
枚舉單例public enum Singleton { //定義一個(gè)枚舉的元素,它就是Singleton的一個(gè)實(shí)例 INSTANCE; public void doSomething() { } } //使用 public static void main(String[] args){ Singleton singleton = Singleton.instance; singleton.doSomething(); }
寫(xiě)法簡(jiǎn)單是枚舉單例最大的優(yōu)點(diǎn),枚舉在Java中與普通的類(lèi)是一樣的,不僅能夠有字段,還能夠有自己的方法。最重要的是默認(rèn)枚舉實(shí)例的創(chuàng)建時(shí)線程安全的,并且在任何情況下它都是一個(gè)單例。
為什么這么說(shuō)呢?在上述的幾種單例模式實(shí)現(xiàn)中,在一個(gè)情況下它們會(huì)出現(xiàn)重新創(chuàng)建對(duì)象的情況,那就是反序列化。
通過(guò)序列化可以將一個(gè)單例的實(shí)例對(duì)象寫(xiě)到磁盤(pán),然后再讀回來(lái),從而有效地獲得一個(gè)實(shí)例。即使構(gòu)造函數(shù)時(shí)私有的,反序列化時(shí)依然可以通過(guò)特殊的途徑去創(chuàng)建類(lèi)的一個(gè)新的實(shí)例,相當(dāng)于調(diào)用該類(lèi)的構(gòu)造函數(shù)。反序列化操作提供一個(gè)很特別的鉤子函數(shù),類(lèi)中具有一個(gè)私有的、被實(shí)例化的方法readResolve(),這個(gè)方法可以讓開(kāi)發(fā)人員控制對(duì)象的反序列化。例如,上述幾個(gè)實(shí)例中如果要杜絕單例對(duì)象在被反序列化時(shí)重新生成對(duì)象,那么必須加入如下方法:
private Object readResolve() throws ObjectStreamException { return instance; }
也就是在readResolve方法中將instance對(duì)象返回,而不是默認(rèn)的重新生成一個(gè)新的對(duì)象。而對(duì)于枚舉并不存在這樣的問(wèn)題,因?yàn)榧词狗葱蛄谢膊粫?huì)重新生成新的實(shí)例。
容器管理單例public class SingletonManager { private static MapobjMap = new HashMap (); public static void registerService(String key,Object instance) { if (!objMap.containsKey(key)) { objMap.put(key); } } public static getInstance(String key) { return objMap.get(key); } }
在程序的初始,將多種單例類(lèi)注入到一個(gè)統(tǒng)一的管理類(lèi)中,在使用根據(jù)key獲取對(duì)應(yīng)類(lèi)型的對(duì)象,這種方式使得我們可以管理很多類(lèi)型的單例,并且在使用它們的時(shí)候可以通過(guò)統(tǒng)一的接口進(jìn)行獲取操作操作,降低用戶(hù)的使用成本,也對(duì)用戶(hù)隱藏了具體實(shí)現(xiàn),降低了耦合度。
總結(jié)單例模式是運(yùn)用頻率很高的模式,但是,由于在客戶(hù)端通常沒(méi)有高并發(fā)的情況,因此,選擇哪種實(shí)現(xiàn)方式并不會(huì)有太大的影響。即便如此,出于效率考慮,推薦使用雙重校驗(yàn)鎖和靜態(tài)內(nèi)部類(lèi)單例模式。
優(yōu)點(diǎn)
由于單例模式在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開(kāi)支,特別是一個(gè)對(duì)象需要頻繁創(chuàng)建、銷(xiāo)毀時(shí),而且創(chuàng)建或者銷(xiāo)毀時(shí)性能又無(wú)法優(yōu)化,單例模式的優(yōu)勢(shì)就非常明顯。
由于單例模式只生成一個(gè)實(shí)例,所以,減少了系統(tǒng)的性能開(kāi)銷(xiāo),當(dāng)一個(gè)對(duì)象的產(chǎn)生需要比較多的資源時(shí),如讀取配置、產(chǎn)生依賴(lài)對(duì)象時(shí),則可以通過(guò)在應(yīng)用啟動(dòng)時(shí)直接產(chǎn)生一個(gè)單例對(duì)象,然后用永駐內(nèi)存的方式解決。
單例模式可以避免對(duì)資源的多重占用,例如一個(gè)寫(xiě)文件操作,由于只有一個(gè)實(shí)例存在內(nèi)存中,避免對(duì)同一個(gè)資源文件的同時(shí)寫(xiě)操作。
單例模式可以在系統(tǒng)設(shè)置全局訪問(wèn)點(diǎn),優(yōu)化和共享資源訪問(wèn),例如,可以設(shè)計(jì)一個(gè)單例類(lèi),負(fù)責(zé)所有數(shù)據(jù)表的映射處理。
缺點(diǎn):
單例模式一般沒(méi)有接口,擴(kuò)展很困難,若要擴(kuò)展,除了修改代碼基本沒(méi)有第二種途徑可以實(shí)現(xiàn)。
在Android中,單例對(duì)象如果持有Context,那么很容易引發(fā)內(nèi)存泄露,此時(shí)需要注意傳給單例對(duì)象的Context最好是Application Context。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/66125.html
摘要:總結(jié)單例是運(yùn)用頻率很高的模式,因?yàn)榭蛻?hù)端沒(méi)有高并發(fā)的情況,選擇哪種方式并不會(huì)有太大的影響,出于效率考慮,推薦使用和靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)單例模式。 單例模式介紹 單例模式是應(yīng)用最廣的模式之一,也可能是很多人唯一會(huì)使用的設(shè)計(jì)模式。在應(yīng)用單例模式時(shí),單例對(duì)象的類(lèi)必須保證只用一個(gè)實(shí)例存在。許多時(shí)候整個(gè)系統(tǒng)只需要一個(gè)全局對(duì)象,這樣有利于我么能協(xié)調(diào)整個(gè)系統(tǒng)整體的行為。 單例模式的使用場(chǎng)景 確保某個(gè)類(lèi)有且...
摘要:所以,在版本前,雙重檢查鎖形式的單例模式是無(wú)法保證線程安全的。 單例模式可能是代碼最少的模式了,但是少不一定意味著簡(jiǎn)單,想要用好、用對(duì)單例模式,還真得費(fèi)一番腦筋。本文對(duì)Java中常見(jiàn)的單例模式寫(xiě)法做了一個(gè)總結(jié),如有錯(cuò)漏之處,懇請(qǐng)讀者指正。 餓漢法 顧名思義,餓漢法就是在第一次引用該類(lèi)的時(shí)候就創(chuàng)建對(duì)象實(shí)例,而不管實(shí)際是否需要?jiǎng)?chuàng)建。代碼如下: public class Singleton...
摘要:一基礎(chǔ)接口的意義百度規(guī)范擴(kuò)展回調(diào)抽象類(lèi)的意義想不想通過(guò)一線互聯(lián)網(wǎng)公司面試文檔整理為電子書(shū)掘金簡(jiǎn)介谷歌求職記我花了八個(gè)月準(zhǔn)備谷歌面試掘金原文鏈接翻譯者 【面試寶典】從對(duì)象深入分析 Java 中實(shí)例變量和類(lèi)變量的區(qū)別 - 掘金原創(chuàng)文章,轉(zhuǎn)載請(qǐng)務(wù)必保留原出處為:http://www.54tianzhisheng.cn/... , 歡迎訪問(wèn)我的站點(diǎn),閱讀更多有深度的文章。 實(shí)例變量 和 類(lèi)變量...
閱讀 3744·2021-11-19 09:56
閱讀 1562·2021-09-22 15:11
閱讀 1223·2019-08-30 15:55
閱讀 3431·2019-08-29 14:02
閱讀 3045·2019-08-29 11:07
閱讀 493·2019-08-28 17:52
閱讀 3231·2019-08-26 13:59
閱讀 492·2019-08-26 13:53