編輯:關於Android編程
單例模式是應用最廣的模式,也是我最先知道的一種設計模式,在深入了解單例模式之前,每當遇到如:getInstance()這樣的創建實例的代碼時,我都會把它當做一種單例模式的實現。其實經常使用的圖片加載框架ImageLoader的實例創建就是使用了單例模式,因為這個ImageLoader中含有線程池、緩存系統、網絡請求,很消耗資源,不應該創建多個對象,這時候就需要用到單例模式。
ImageLoader的創建代碼如下:
ImageLoader.getInstance();// 在自己的Application中創建全局實例
.....
//getInstance()執行的源碼
public static ImageLoader getInstance() {
if(instance == null) {//雙重校驗DCL單例模式
Class var0 = ImageLoader.class;
synchronized(ImageLoader.class) {//同步代碼塊
if(instance == null) {
instance = new ImageLoader();//創建一個新的實例
}
}
}
return instance;//返回一個實例
}
因此,在我們創建一個對象需要消耗過多的資源時,便可以考慮使用單例模式。
單例模式的定義是它應該保證一個類僅有一個實例,同時這個類還必須提供一個訪問該類的全局訪問點。如下圖是單例模式的結構圖:
實現單例模式有以下幾個關鍵點:
(1)其構造函數不對外開放,一般為private;
(2)通過一個靜態方法或者枚舉返回單例類對象;
(3)確保單例類的對象有且只有一個,尤其要注意多線程的場景;
(4)確保單例類對象在反序列化時不會重新創建對象;<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPs2ouf29q7WlwP3A4LXEubnU7Lqvyv3LvdPQu6+jrMq5tcO/zbuntsuyu8Tczai5/W5ld7XE0M7KvcrWtq+5udTstaXA/cDgtcS21M/zoaO1pcD9wOC74db3tq+xqcK20ru49rmr09C1xL6yzKy3vbeoo6y/zbuntsu199PD1eK49r6yzKy1xLe9t6i78cihtb21pcD9wOC1xM6o0rvKtcD9oaPU2rvxyKHV4rj2taXA/cDgtcTKsbry0OjSqsi3saPV4rj2uf2zzMrHz9+zzLCyyKu1xKGjPC9wPg0KPGgyIGlkPQ=="三-單例模式的七種實現方式">三 單例模式的七種實現方式
(1)懶漢式(線程不安全)
//懶漢式單例類.在第一次調用的時候實例化自己
public class Singleton {
//私有的構造函數
private Singleton() {}
//私有的靜態變量
private static Singleton single=null;
//暴露的公有靜態方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
懶漢式(線程不安全)的單例模式分為三個部分:私有的構造方法,私有的全局靜態變量,公有的靜態方法。
其中起到重要作用的是靜態修飾符static關鍵字,我們知道在程序中,任何變量或者代碼都是在編譯時由系統自動分配內存來存儲的,而所謂靜態就是指在編譯後所分配的內存會一直存在,直到程序退出內存才會釋放這個空間,因此也就保證了單例類的實例一旦創建,便不會被系統回收,除非手動設置為null。
這種方式創建的缺點是存在線程不安全的問題,解決的辦法就是使用synchronized 關鍵字,便是單例模式的第二種寫法了。
(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 關鍵字,也就是告訴Java(JVM)getInstance是一個同步方法。
同步的意思是當兩個並發線程訪問同一個類中的這個synchronized同步方法時, 一個時間內只能有一個線程得到執行,另一個線程必須等待當前線程執行完才能執行,因此同步方法使得線程安全,保證了單例只有唯一個實例。
但是它的缺點在於每次調用getInstance()都進行同步,造成了不必要的同步開銷。這種模式一般不建議使用。
(3)餓漢模式(線程安全)
代碼實現如下:
//餓漢式單例類.在類初始化時,已經自行實例化
public class Singleton {
//static修飾的靜態變量在內存中一旦創建,便永久存在
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
餓漢式在類創建的同時就已經創建好一個靜態的對象供系統使用,以後不再改變,所以天生是線程安全的。其中instance=new Singleton()可以寫成:
static {
instance = new Singleton();
}
屬於變種的餓漢單例模式,也是基於classloder機制避免了多線程的同步問題,instance在類裝載時就實例化了。
(4)DCL雙重校驗模式
public class Singleton {
private static Singleton singleton; //靜態變量
private Singleton (){} //私有構造函數
public static Singleton getInstance() {
if (singleton == null) { //第一層校驗
synchronized (Singleton.class) {
if (singleton == null) { //第二層校驗
singleton = new Singleton();
}
}
}
return singleton;
}
}
這種模式的亮點在於getInstance()方法上,其中對singleton 進行了兩次判斷是否空,第一層判斷是為了避免不必要的同步,第二層的判斷是為了在null的情況下才創建實例。具體我們來分析一下:
假設線程A執行到了singleton = new Singleton(); 語句,這裡看起來是一句代碼,但是它並不是一個原子操作,這句代碼最終會被編譯成多條匯編指令,它大致會做三件事情:
(a)給Singleton的實例分配內存
(b)調用Singleton()的構造函數,初始化成員字段;
(c)將singleton對象指向分配的內存空間(即singleton不為空了);
但是由於Java編譯器允許處理器亂序執行,以及在jdk1.5之前,JMM(Java Memory Model:java內存模型)中Cache、寄存器、到主內存的回寫順序規定,上面的步驟b 步驟c的執行順序是不保證了。也就是說執行順序可能是a-b-c,也可能是a-c-b,如果是後者的指向順序,並且恰恰在c執行完畢,b尚未執行時,被切換到線程B中,這時候因為singleton在線程A中執行了步驟c了,已經非空了,所以,線程B直接就取走了singleton,再使用時就會出錯。這就是DCL失效問題。
但是在JDK1.5之後,官方給出了volatile關鍵字,將singleton定義的代碼改成:
private volatile static Singleton singleton; //使用volatile 關鍵字
這樣就解決了DCL失效的問題。
(5)靜態內部類單例模式
public class Singleton {
private Singleton (){} ;//私有的構造函數
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
//定義的靜態內部類
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton(); //創建實例的地方
}
}
當第一次加載Singleton 類的時候並不會初始化INSTANCE ,只有第一次調用Singleton 的getInstance()方法時才會導致INSTANCE 被初始化。因此,第一次調用getInstance()方法會導致虛擬機加載SingletonHolder 類,這種方式不僅能夠確保單例對象的唯一性,同時也延遲了單例的實例化。
(6)枚舉單例
前面的幾種單例模式實現方式,一般都會稍顯麻煩,或是在某些特定的情況下出現一些狀況。下面介紹枚舉單例模式的實現:
public enum Singleton { //enum枚舉類
INSTANCE;
public void whateverMethod() {
}
}
枚舉單例模式最大的優點就是寫法簡單,枚舉在java中與普通的類是一樣的,不僅能夠有字段,還能夠有自己的方法,最重要的是默認枚舉實例是線程安全的,並且在任何情況下,它都是一個單例。即使是在反序列化的過程,枚舉單例也不會重新生成新的實例。而其他幾種方式,必須加入如下方法:
private Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
才能保證反序列化時不會生成新的對象。
(7)使用容器實現單例模式
除了上述幾種常見的實現單例的方式,還有另一類的實現,代碼如下:
public class SingletonManager {
private static Map objMap = new HashMap();//使用HashMap作為緩存容器
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;//第一次是存入Map
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;//返回與key相對應的對象
}
}
在程序的初始,將多種單例模式注入到一個統一的管理類中,在使用時根據key獲取對應類型的對象。
在Android源碼中,APP啟動的時候,虛擬機第一次加載該類時會注冊各種ServiceFetcher,比如LayoutInflater Service。將這些服務以鍵值對的形式存儲在一個HashMap中,用戶使用時只需要根據key來獲取到對應的ServiceFetcher,然後通過ServiceFetcher對象的getService函數獲取具體的服務對象。當第一次獲取時,會調用ServiceFetcher的creatService函數創建服務對象,然後將該對象緩存到一個列表中,下次再取時直接從緩存中獲取,避免重復創建對象,從而達到單例的效果。Android中的系統核心服務以單例形式存在,減少了資源消耗。
總結:不管以哪種形式實現單例模式,它們的核心原理是將構造函數私有化,並且通過靜態公有方法獲取一個唯一的實例,在這個獲取的過程中必須保證線程的安全,同時也要防止反序列化導致重新生成實例對象。
在上一篇文章中介紹了使用非RxJava環境下,使用Handler機制SyncBarrier的特性實現預加載功能的方法。在RxJava的環境下使用BehaviorSubje
先看效果圖 如何使用 import java.text.DateFormat; import java.text.ParseException; import jav
小米手環有一個免密碼支付功能,作為忠實米粉小米手環免密碼支付怎麼用都不會有點說不過去,那麼小米手環免密碼支付怎麼設置?小米手環免密碼支付怎麼用?下面為大家帶
Android平台api沒有特意為換膚提供一套簡便的機制,這可能是外國的軟件更注重功能和易用,不流行換膚。系統不提供直接支持,只能自行研究。 換膚,可以認為是動態替換資源