概念:
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、資源加載和性能:
餓漢式在類創建的同時就實例化一個靜態對象出來,不管之後會不會使用這個單例,都會佔據一定的內存,但是相應的,在第一次調用時速度也會更快,因爲其資源已經初始化完成,
而懶漢式顧名思義,會延遲加載,在第一次使用該單例的時候纔會實例化對象出來,第一次調用時要做初始化,如果要做的工作比較多,性能上會有些延遲,之後就和餓漢式一樣了。
什麼是線程安全?
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。