單例模式Singleton

概念:
  Java中單例模式是一種常見的設計模式,單例模式主要有兩種寫法:懶漢式單例、餓漢式單例。
  單例模式實現步驟:
  1、單例類只能有一個實例。
  2、單例類必須自己創建自己的唯一實例。
  3、單例類必須給所有其他對象提供這一實例。

  單例模式確保某個類只有一個實例,而且自行實例化並向整個系統提供這個實例。在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個打印機,但只能有一個Printer Spooler,以避免兩個打印作業同時輸出到打印機中。每臺計算機可以有若干通信端口,系統應當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調用。總之,選擇單例模式就是爲了避免不一致狀態,避免政出多頭。

懶漢式單例

我們以Volley中RequestQueue設計單例模式,爲避免線程安全加入了同步鎖

方法1:

public class CustomVolley {
//    1、單例類只能有一個實例
    private static CustomVolley ourInstance;
//   聲明全局的RequestQueue
    public static RequestQueue mRequestQueue;

    private CustomVolley(Context context) {
//        在私有的構造方法中進行賦值
        if (mRequestQueue == null) {
            String sdPath = null;
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                sdPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "myVolleyCache";
            } else {
                sdPath = context.getCacheDir().getAbsolutePath() + File.separator + "myVolleyCache";
            }
            File rootDirectory = new File(sdPath);
            if (!rootDirectory.exists()) {
//                文件夾不存在,創建
                rootDirectory.mkdirs();
            }
            Cache cache = new DiskBasedCache(rootDirectory, 1024 * 1024 * 200);
//            如果使用HurlStack,RequestQueue聯網請求方式採用HttpUrlConnection
//            HttpClientStack 聯網請求方式,採用HttpClient
            HttpStack httpStack = new HurlStack();
            Network network = new BasicNetwork(httpStack);
            mRequestQueue = new RequestQueue(cache, network, 6);
        }
    }

//    3、單例類必須給所有其他對象提供這一實例。
//    同步鎖雙重鎖定,避免線程安全問題
    public static CustomVolley getInstance(Context context) {
//    2、單例類必須自己創建自己的唯一實例,通過私有的構造方法創建
        if (ourInstance == null) {
            synchronized (CustomVolley.class) {
                if (ourInstance == null) {
                    ourInstance = new CustomVolley(context);
                }
            }
        }
        return ourInstance;
    }
}

方法2:

public class CustomVolley {
//    1、單例類只能有一個實例
    private static CustomVolley ourInstance;
//   聲明全局的RequestQueue
    public static RequestQueue mRequestQueue;

    private CustomVolley(Context context) {
//        在私有的構造方法中進行賦值
        if (mRequestQueue == null) {
            String sdPath = null;
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                sdPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "myVolleyCache";
            } else {
                sdPath = context.getCacheDir().getAbsolutePath() + File.separator + "myVolleyCache";
            }
            File rootDirectory = new File(sdPath);
            if (!rootDirectory.exists()) {
//                文件夾不存在,創建
                rootDirectory.mkdirs();
            }
            Cache cache = new DiskBasedCache(rootDirectory, 1024 * 1024 * 200);
//            如果使用HurlStack,RequestQueue聯網請求方式採用HttpUrlConnection
//            HttpClientStack 聯網請求方式,採用HttpClient
            HttpStack httpStack = new HurlStack();
            Network network = new BasicNetwork(httpStack);
            mRequestQueue = new RequestQueue(cache, network, 6);
        }
    }

//    3、單例類必須給所有其他對象提供這一實例。
//    在方法getInstance上加同步,避免線程安全問題
    public static synchronized CustomVolley getInstance(Context context) {
//    2、單例類必須自己創建自己的唯一實例,通過私有的構造方法創建
        if (ourInstance == null) {
             ourInstance = new CustomVolley(context);
         }
        return ourInstance;
    }
}

方法3:

public class Singleton {    
// 靜態方法,優先執行,上下文就無法傳遞進去
    private static class LazyHolder {    
       private static final Singleton instance= new Singleton();    
    }    
    private Singleton (){
    // 實現相應的方法
    }    
    public static final Singleton getInstance() {    
       return LazyHolder.instance;    
    }    
}  

這種寫法既實現了線程安全,又避免了同步帶來的性能影響,缺點是難以傳參數,比如上下文mContext

餓漢式單例
餓漢式在類創建的同時就已經創建好一個靜態的對象供系統使用,以後不再改變,所以天生是線程安全的

//餓漢式單例類在類初始化時,已經自行實例化   
public class Singleton {  
    private Singleton() {}
    //1、單例類只能有一個實例 ,私有構造方法來限定
    //2、單例類必須自己創建自己的唯一實例
    private static final Singleton single = new Singleton();  
    //靜態工廠方法   
    //3、單例類必須給所有其他對象提供這一實例。
    public static Singleton getInstance() {  
        return single;  
    }  
} 

1、線程安全:

餓漢式天生就是線程安全的,可以直接用於多線程而不會出現問題,

懶漢式本身是非線程安全的,爲了實現線程安全有幾種寫法,分別是上面的1、2、3,這三種實現在資源加載和性能方面有些區別。
性能區別如下:
第1種,在getInstance中做了兩次null檢查,確保了只有第一次調用單例的時候纔會做同步,這樣也是線程安全的,同時避免了每次都同步的性能損耗

第2種,在方法調用上加了同步,雖然線程安全了,但是每次都要同步,會影響性能,畢竟99%的情況下是不需要同步的,

第3種,利用了classloader的機制來保證初始化instance時只有一個線程,所以也是線程安全的,同時沒有性能損耗,所以一般我傾向於使用這一種。

2、資源加載和性能:

餓漢式在類創建的同時就實例化一個靜態對象出來,不管之後會不會使用這個單例,都會佔據一定的內存,但是相應的,在第一次調用時速度也會更快,因爲其資源已經初始化完成,

而懶漢式顧名思義,會延遲加載,在第一次使用該單例的時候纔會實例化對象出來,第一次調用時要做初始化,如果要做的工作比較多,性能上會有些延遲,之後就和餓漢式一樣了。

什麼是線程安全?

如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

發佈了44 篇原創文章 · 獲贊 23 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章