Android 單例模式必知必會

      網友投稿 927 2025-04-02

      一、概念

      單例模式是運用最廣泛的設計模式之一,在應用這個模式時,單例模式的類必須保證只有一個實例存在。多用于整個程序只需要有一個實例,通常很消耗資源的類,比如線程池,緩存,網絡請求,IO操作,訪問數據庫等。由于類比較耗資源,所以沒必要讓它構造多個實例,這種就是單例模式比較好的使用場景。

      1.1 單例類

      單例模式(Singleton Pattern):一個類有且僅有一個實例,并且自行實例化向整個系統提供,也稱為單例類。

      單例模式有三個要點:

      1.某個類只能有一個實例。

      2.必須自行創建這個實例。

      3.必須給所有其他對象提供這一實例。

      一、概念

      單例模式是運用最廣泛的設計模式之一,在應用這個模式時,單例模式的類必須保證只有一個實例存在。多用于整個程序只需要有一個實例,通常很消耗資源的類,比如線程池,緩存,網絡請求,IO操作,訪問數據庫等。由于類比較耗資源,所以沒必要讓它構造多個實例,這種就是單例模式比較好的使用場景。

      1.1 單例類

      單例模式(Singleton Pattern):一個類有且僅有一個實例,并且自行實例化向整個系統提供,也稱為單例類。

      單例模式有三個要點:

      1.某個類只能有一個實例。

      1.某個類只能有一個實例。

      2.必須自行創建這個實例。

      2.必須自行創建這個實例。

      3.必須給所有其他對象提供這一實例。

      3.必須給所有其他對象提供這一實例。

      具體實現角度來說就是以下幾點:

      1.單例模式的類只提供私有的構造函數。

      1.單例模式的類只提供私有的構造函數。

      2.通過一個靜態方法或者枚舉返回單例類對象。

      2.通過一個靜態方法或者枚舉返回單例類對象。

      3.確保單例類有且只有一個靜態私有對象,尤其是在多線程環境下。

      3.確保單例類有且只有一個靜態私有對象,尤其是在多線程環境下。

      4.提供了一個靜態的公有的函數用于創建或獲取它本身的靜態私有對象。

      4.提供了一個靜態的公有的函數用于創建或獲取它本身的靜態私有對象。

      5.確保單例類對象在反序列化時不會重新構建對象。

      5.確保單例類對象在反序列化時不會重新構建對象。

      在單例類的內部實現只生成一個實例,同時它提供一個靜態的getInstance()工廠方法,讓客戶可以訪問它的唯一實例;為了防止在外部對其實例化,將其構造函數設計為私有;在單例類內部定義了一個Singleton類型的靜態對象,作為外部共享的唯一實例。

      1.2 優缺點

      1.2.1 優點

      1.單例模式提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它。

      1.單例模式提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它。

      2.由于在系統內存中只存在一個對象,因此可以節約系統資源,對于一些需要頻繁創建和銷毀的對象單例模式無疑可以提高系統的性能。

      2.由于在系統內存中只存在一個對象,因此可以節約系統資源,對于一些需要頻繁創建和銷毀的對象單例模式無疑可以提高系統的性能。

      3.允許可變數目的實例。基于單例模式我們可以進行擴展,使用與單例控制相似的方法來獲得指定個數的對象實例,既節省系統資源,又解決了單例單例對象共享過多有損性能的問題。

      3.允許可變數目的實例?;趩卫J轿覀兛梢赃M行擴展,使用與單例控制相似的方法來獲得指定個數的對象實例,既節省系統資源,又解決了單例單例對象共享過多有損性能的問題。

      1.2.2 缺點

      1.由于單例模式中沒有抽象層,因此單例類的擴展有很大的困難。

      1.由于單例模式中沒有抽象層,因此單例類的擴展有很大的困難。

      2.單例類的職責過重,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的創建和產品的本身的功能融合到一起。

      2.單例類的職責過重,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的創建和產品的本身的功能融合到一起。

      3.現在很多面向對象語言(如Java、C#)的運行環境都提供了自動垃圾回收的技術,因此,如果實例化的共享對象長時間不被利用,系統會認為它是垃圾,會自動銷毀并回收資源,下次利用時又將重新實例化,這將導致共享的單例對象狀態的丟失。

      3.現在很多面向對象語言(如Java、C#)的運行環境都提供了自動垃圾回收的技術,因此,如果實例化的共享對象長時間不被利用,系統會認為它是垃圾,會自動銷毀并回收資源,下次利用時又將重新實例化,這將導致共享的單例對象狀態的丟失。

      二、創建單例模式的方法

      2.1 餓漢式

      強調餓,那么在創建對象實例的時候就比較著急,餓了嘛,于是在裝載類的時候就創建對象實例。

      這種方法非常簡單,因為單例的實例被聲明成 static 和 final 變量,在第一次加載類到內存中時就會初始化,所以創建實例本身是線程安全的。

      public class SingletonHungry { //類加載時就初始化 private static final SingletonHungry singleton = new SingletonHungry(); private SingletonHungry(){} public static SingletonHungry getInstance(){ return singleton; } }

      缺點是它不是一種懶加載模式,即使客戶端沒有調用 getInstance()方法,單例會在加載類后一開始就被初始化。

      餓漢式的創建方式在一些場景中將無法使用:如 Singleton 實例的創建是依賴參數或者配置文件的,在 getInstance() 之前必須調用某個方法設置參數給它,那樣這種單例寫法就無法使用了。

      2.2 懶漢式

      強調懶,那么在創建對象實例的時候就不著急,什么時候用什么時候創建。所以在裝載對象的時候不創建對象實例。

      2.2.1 懶漢式(非線程安全)

      public class SingletonLazy { private static SingletonLazy singletonLazy; private SingletonLazy(){} public static SingletonLazy getInstance(){ if (singletonLazy == null) { singletonLazy = new SingletonLazy(); } return singletonLazy; } }

      這段代使用了懶加載模式,但是卻存在致命的問題。當有多個線程并行調用 getInstance() 的時候,就會創建多個實例。也就是說在多線程下不能正常工作。那么怎么解決?最簡單的方法是給 getInstance() 方法加個同步鎖(synchronized)。

      2.2.2 懶漢式(線程安全)

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

      上面通過添加 synchronized 關鍵字,使得getInstance()是一個同步方法,保證多線程情況下單例對象的唯一性。

      雖然做到了線程安全,并且解決了多實例的問題,但是它并不高效。因為在任何時候只能有一個線程調用 getInstance() 方法。但是同步操作只需要在第一次調用時才被需要,即第一次創建單例實例對象時。這就引出了雙重檢驗鎖。

      2.3 雙重檢驗鎖

      雙重檢驗鎖模式(double checked locking pattern),是一種使用同步塊加鎖的方法。程序員稱其為雙重檢查鎖,也是網上使用畢竟頻繁的一種方式。

      為什么叫雙重檢查鎖?因為會有兩次檢查 instance == null,一次是在同步塊外,一次是在同步塊內。為什么在同步塊內還要再檢驗一次?因為可能會有多個線程一起進入同步塊外的 if,避免不必要的同步。如果在同步塊內不進行二次檢驗的話就會生成多個實例,避免生成多個實例。

      public class SingletonDCL { private static SingletonDCL singleton; private SingletonDCL(){} public static SingletonDCL getInstance(){ if (singleton == null) { synchronized (SingletonDCL.class){ if (singleton == null) { singleton = new SingletonDCL(); } } } return singleton; } }

      這段代碼看起來很完美有著雙重檢查,但很可惜,它是有問題。主要在于 singleton = new SingletonDCL()。事實上在 JVM 中這句話大概做了下面 3 件事情:

      1.給 singleton 分配內存。

      1.給 singleton 分配內存。

      2.調用 SingletonDCL 的構造函數來初始化成員變量。

      2.調用 SingletonDCL 的構造函數來初始化成員變量。

      3.將singleton對象指向分配的內存空間(執行完這步 singleton 就為非 null)。

      3.將singleton對象指向分配的內存空間(執行完這步 singleton 就為非 null)。

      但是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第2步和第3步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執行完畢、但 2 未執行之前,被線程二搶占了,這時 singleton 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 singleton(第2步未執行),然后使用,然后順理成章地報錯。

      我們只需要將 singleton 變量聲明成 volatile 就可以了。

      public class SingletonDCL { private volatile static SingletonDCL singleton;//變量聲明成volatile private SingletonDCL(){} public static SingletonDCL getInstance(){ if (singleton == null) { synchronized (SingletonDCL.class){ if (singleton == null) { singleton = new SingletonDCL(); } } } return singleton; } }

      使用 volatile 的主要原因是其有一個特性:禁止指令重排序優化。也就是說,在 volatile 變量的賦值操作后面會有一個內存屏障(生成的匯編代碼上),讀操作不會被重排序到內存屏障之前。比如上面的例子,取操作必須在執行完 1-2-3 之后或者 1-3-2 之后,不存在執行到 1-3 然后取到值的情況。

      Android 單例模式必知必會

      當然 volatile 變量還有一個規則:對一個變量的寫操作先行發生于后面對這個變量的讀操作(這里的"后面"是時間上的先后順序)。

      2.4 靜態內部類

      public class SingletonNested { //靜態內部類 private static class SingletonHolder{ private static final SingletonNested singleton = new SingletonNested(); } private SingletonNested(){} public static SingletonNested getInstance(){ return SingletonHolder.singleton; } }

      使用JVM本身機制保證了線程安全問題。由于靜態單例對象沒有作為Singleton的成員變量直接實例化,因此類加載時不會實例化SingletonNested,第一次調用getInstance()時將加載內部類SingletonHolder,在該內部類中定義了一個static類型的變量singleton,此時會首先初始化這個成員變量,由Java虛擬機來保證其線程安全性,確保該成員變量只能初始化一次。由于getInstance()方法沒有任何線程鎖定,因此其性能不會造成任何影響。 s

      由于 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的,同時讀取實例的時候不會進行同步,沒有性能缺陷,也不依賴 JDK 版本。

      2.5 枚舉

      public enum SingletonEnum { SINGLETON; public void doSomeThing() { } }

      我們可以通過SingletonEnum.SINGLETON來訪問實例,這比調用getInstance()方法簡單多了。創建枚舉默認就是線程安全的,所以不需要擔心double checked locking,而且還能防止反序列化導致重新創建新的對象。

      小結

      單例模式不管用那種方式實現,核心思想都相同:

      1.構造函數私有化,通過一次靜態方法獲取一個唯一實例

      2.線程安全

      使用場景:

      1.需要頻繁的進行創建和銷毀的對象。

      2.創建對象時耗時過多或耗費資源過多,但又經常用到的對象。

      3.工具類對象。

      4.頻繁訪問數據庫或文件的對象。

      一般情況下直接使用餓漢式就好了,當然推薦使用文中DCL方式和靜態內部類的方式來創建單例模式。如果涉及到反序列化創建對象時會試著使用枚舉的方式來實現單例。當然,枚舉單例的優點就是簡單,但是大部分應用開發很少用枚舉,可讀性并不是很高,不建議用。

      三、擴展

      3.1 防止反序列化

      上文使用枚舉可以防止反序列化導致重新創建新的對象。那么其他幾種實現單例模式的方式怎么方式防止反序列化導致重新創建新的對象?那就是反序列化??梢詤⒖夹蛄谢晃?。

      反序列化操作提供了一個很特別的鉤子函數,類中具有一個私有的readResolve()函數,這個函數可以讓開發人員控制對象的反序列化。

      public class SingletonDCL implements Serializable { private volatile static SingletonDCL singleton;//變量聲明成volatile ... private Object readResolve() throws ObjectStreamException { return singleton; } }

      在readResolve方法中將單例對象返回,而不是重新生成一個新對象。

      3.2 volatile 關鍵字

      Java內存模型規定了所有的變量都存儲在主內存中。每條線程中還有自己的工作內存,線程的工作內存中保存了被該線程所使用到的變量(這些變量是從主內存中拷貝而來)。線程對變量的所有操作(讀取,賦值)都必須在工作內存中進行。不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成。

      作用

      1.線程可見性

      當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。

      2.指令重排序

      沒加之前,指令是并發執行的,第一個線程執行到一半另一個線程可能開始執行了。加了volatile關鍵字后,不同線程是按照順序一步一步執行的。例如上面2.3 雙重檢驗鎖。

      Android Java 任務調度 通用安全

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:銷售統計報表模板(銷售統計報表表格
      下一篇:為什么我們說云原生時代,企業數字化轉型更需要做好 API 全生命周期管理?
      相關文章
      亚洲熟女乱色一区二区三区| 日韩亚洲国产综合高清| 亚洲一区二区三区丝袜| 亚洲国产综合人成综合网站00| 亚洲av之男人的天堂网站| 亚洲人成网77777色在线播放 | 亚洲男人第一无码aⅴ网站| 含羞草国产亚洲精品岁国产精品 | 久久精品国产亚洲av麻豆蜜芽 | av无码东京热亚洲男人的天堂| 亚洲av成人无码网站…| 亚洲av日韩综合一区二区三区| 亚洲成aⅴ人片久青草影院按摩| 久久久久亚洲国产AV麻豆| 亚洲AV日韩AV一区二区三曲| 日韩色日韩视频亚洲网站| 国产AV无码专区亚洲AV麻豆丫| 亚洲a∨无码精品色午夜| 国产成人高清亚洲一区久久| 国产亚洲高清在线精品不卡| 亚洲成人国产精品| 亚洲一区二区三区在线播放| 国产黄色一级毛片亚洲黄片大全| 亚洲宅男天堂在线观看无病毒| 亚洲精品国偷自产在线| 久久久久亚洲Av片无码v| 久久精品国产亚洲AV大全| 亚洲色偷偷av男人的天堂| 亚洲人成伊人成综合网久久| 亚洲熟妇丰满xxxxx| 无码专区一va亚洲v专区在线 | 亚洲精品在线不卡| 国产精品亚洲专区在线观看| 亚洲欧美日韩中文字幕一区二区三区 | 亚洲中文字幕成人在线| 亚洲日韩亚洲另类激情文学| 亚洲国产精品日韩av不卡在线| 日日摸日日碰夜夜爽亚洲| 亚洲性久久久影院| 亚洲成A人片在线观看无码不卡| 亚洲图片一区二区|