溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何解析Android Java語言中單例這種設(shè)計(jì)模式

發(fā)布時(shí)間:2021-11-26 17:19:21 來源:億速云 閱讀:154 作者:柒染 欄目:移動(dòng)開發(fā)

本篇文章為大家展示了如何解析Android Java語言中單例這種設(shè)計(jì)模式,內(nèi)容簡明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。

概念

單例模式,又稱單件模式或者單子模式,指的是一個(gè)類只有一個(gè)實(shí)例,并且提供一個(gè)全局訪問點(diǎn)。

實(shí)現(xiàn)思路

  • 在單例的類中設(shè)置一個(gè)private靜態(tài)變量sInstance,sInstance類型為當(dāng)前類,用來持有單例***的實(shí)例。

  • 將(無參數(shù))構(gòu)造器設(shè)置為private,避免外部使用new構(gòu)造多個(gè)實(shí)例。

  • 提供一個(gè)public的靜態(tài)方法,如getInstance,用來返回該類的***實(shí)例sInstance。

其中上面的單例的實(shí)例可以有以下幾種創(chuàng)建形式,每一種實(shí)現(xiàn)都需要保證實(shí)例的***性。

餓漢式

餓漢式指的是單例的實(shí)例在類裝載時(shí)進(jìn)行創(chuàng)建。如果單例類的構(gòu)造方法中沒有包含過多的操作處理,餓漢式其實(shí)是可以接受的。

餓漢式的常見代碼如下,當(dāng)SingleInstance類加載時(shí)會(huì)執(zhí)行

private static SingleInstance sInstance = new SingleInstance();

初始化了***的實(shí)例,然后getInstance()直接返回sInstance即可。

public class SingleInstance {   private static SingleInstance sInstance = new SingleInstance();      private SingleInstance() {   }      public static SingleInstance getInstance() {       return sInstance;   } }

餓漢式的問題

  • 如果構(gòu)造方法中存在過多的處理,會(huì)導(dǎo)致加載這個(gè)類時(shí)比較慢,可能引起性能問題。

  • 如果使用餓漢式的話,只進(jìn)行了類的裝載,并沒有實(shí)質(zhì)的調(diào)用,會(huì)造成資源的浪費(fèi)。

懶漢式

懶漢式指的是單例實(shí)例在***次使用時(shí)進(jìn)行創(chuàng)建。這種情況下避免了上面餓漢式可能遇到的問題。

但是考慮到多線程的并發(fā)操作,我們不能簡簡單單得像下面代碼實(shí)現(xiàn)。

public class SingleInstance {   private static SingleInstance sInstance;   private SingleInstance() {   }      public static SingleInstance getInstance() {       if (null == sInstance) {           sInstance = new SingleInstance();       }       return sInstance;   } }

上述的代碼在多個(gè)線程密集調(diào)用getInstance時(shí),存在創(chuàng)建多個(gè)實(shí)例的可能。比如線程A進(jìn)入null == sInstance這段代碼塊,而在A線程未創(chuàng)建完成實(shí)例時(shí),如果線程B也進(jìn)入了該代碼塊,必然會(huì)造成兩個(gè)實(shí)例的產(chǎn)生。

synchronized修飾方法

使用synchrnozed修飾getInstance方法可能是最簡單的一個(gè)保證多線程保證單例***性的方法。
synchronized修飾的方法后,當(dāng)某個(gè)線程進(jìn)入調(diào)用這個(gè)方法,該線程只有當(dāng)其他線程離開當(dāng)前方法后才會(huì)進(jìn)入該方法。所以可以保證getInstance在任何時(shí)候只有一個(gè)線程進(jìn)入。

public class SingleInstance {   private static SingleInstance sInstance;   private SingleInstance() {   }      public static synchronized SingleInstance getInstance() {       if (null == sInstance) {           sInstance = new SingleInstance();       }       return sInstance;   } }

但是使用synchronized修飾getInstance方法后必然會(huì)導(dǎo)致性能下降,而且getInstance是一個(gè)被頻繁調(diào)用的方法。雖然這種方法能解決問題,但是不推薦。

雙重檢查加鎖

使用雙重檢查加鎖,首先進(jìn)入該方法時(shí)進(jìn)行null == sInstance檢查,如果***次檢查通過,即沒有實(shí)例創(chuàng)建,則進(jìn)入synchronized控制的同步塊,并再次檢查實(shí)例是否創(chuàng)建,如果仍未創(chuàng)建,則創(chuàng)建該實(shí)例。

雙重檢查加鎖保證了多線程下只創(chuàng)建一個(gè)實(shí)例,并且加鎖代碼塊只在實(shí)例創(chuàng)建的之前進(jìn)行同步。如果實(shí)例已經(jīng)創(chuàng)建后,進(jìn)入該方法,則不會(huì)執(zhí)行到同步塊的代碼。

public class SingleInstance {   private static volatile SingleInstance sInstance;   private SingleInstance() {   }      public static SingleInstance getInstance() {       if (null == sInstance) {           synchronized (SingleInstance.class) {               if (null == sInstance) {                   sInstance = new SingleInstance();               }           }       }       return sInstance;   } }

volatile是什么

Volatile是輕量級(jí)的synchronized,它在多處理器開發(fā)中保證了共享變量的“可見性”??梢娦缘囊馑际钱?dāng)一個(gè)線程修改一個(gè)共享變量 時(shí),另外一個(gè)線程能讀到這個(gè)修改的值。使用volatile修飾sInstance變量之后,可以確保多個(gè)線程之間正確處理sInstance變量。
關(guān)于volatile,可以訪問深入分析Volatile的實(shí)現(xiàn)原理了解更多。

利用static機(jī)制

在Java中,類的靜態(tài)初始化會(huì)在類被加載時(shí)觸發(fā),我們利用這個(gè)原理,可以實(shí)現(xiàn)利用這一特性,結(jié)合內(nèi)部類,可以實(shí)現(xiàn)如下的代碼,進(jìn)行懶漢式創(chuàng)建實(shí)例。

public class SingleInstance {   private SingleInstance() {   }      public static SingleInstance getInstance() {       return SingleInstanceHolder.sInstance;   }      private static class SingleInstanceHolder {       private static SingleInstance sInstance = new SingleInstance();   } }

關(guān)于這種機(jī)制,可以具體了解雙重檢查鎖定與延遲初始化

好奇問題

真的只有一個(gè)對(duì)象么

其實(shí),單例模式并不能保證實(shí)例的***性,只要我們想辦法的話,還是可以打破這種***性的。以下幾種方法都能實(shí)現(xiàn)。

  • 使用反射,雖然構(gòu)造器為非公開,但是在反射面前就不起作用了。

  • 如果單例的類實(shí)現(xiàn)了cloneable,那么還是可以拷貝出多個(gè)實(shí)例的。

  • Java中的對(duì)象序列化也有可能導(dǎo)致創(chuàng)建多個(gè)實(shí)例。避免使用readObject方法。

  • 使用多個(gè)類加載器加載單例類,也會(huì)導(dǎo)致創(chuàng)建多個(gè)實(shí)例并存的問題。

單例可以繼承么

單例類能否被繼承需要分情況而定。

可以繼承的情況

當(dāng)子類是父類單例類的內(nèi)部類時(shí),繼承是可以的。

public class BaseSingleton {   private static volatile BaseSingleton sInstance;      private BaseSingleton() {          }      public static BaseSingleton getInstance() {       if (null == sInstance) {           synchronized(BaseSingleton.class) {               if (null == sInstance) {                   sInstance = new BaseSingleton();               }           }       }       return sInstance;   }      public static class  MySingleton extends BaseSingleton {          }    }

但是上面僅僅是編譯和執(zhí)行上允許的,但是繼承單例沒有實(shí)際的意義,反而會(huì)變得更加事倍功半,其代價(jià)要大于新寫一個(gè)單例類。感興趣的童鞋可以嘗試折騰一下。

不可以繼承的情況

如果子類為單獨(dú)的類,非單例類的內(nèi)部類的話,那么在編譯時(shí)就會(huì)出錯(cuò)Implicit super constructor BaseSingleton() is not visible for default constructor. Must define an explicit constructor,主要原因是單例類的構(gòu)造器是private,解決方法是講構(gòu)造器設(shè)置為可見,但是這樣做就無法保證單例的***性。所以這種方式不可以繼承。

總的來說,單例類不要繼承。

 

單例 vs static變量

全局靜態(tài)變量也可以實(shí)現(xiàn)單例的效果,但是使用全局變量無法保證只創(chuàng)建一個(gè)實(shí)例,而且使用全局變量的形式,需要團(tuán)隊(duì)的約束,執(zhí)行起來可能會(huì)出現(xiàn)問題。

關(guān)于GC

因?yàn)閱卫愔杏忠粋€(gè)靜態(tài)的變量持有單例的實(shí)例,所以相比普通的對(duì)象,單例的對(duì)象更不容易被GC回收掉。單例對(duì)象的回收應(yīng)該發(fā)生在其類加載器被GC回收掉之后,一般不容易出現(xiàn)。

上述內(nèi)容就是如何解析Android Java語言中單例這種設(shè)計(jì)模式,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI