單例模式
屬于創(chuàng)建型模式
自行完成實(shí)例化搁骑,私有化構(gòu)造函數(shù)
單例模式的目標(biāo)
- 實(shí)例唯一性
- 線程安全性
任何情況都需要確保一個(gè)類只存在一個(gè)實(shí)例篮奄,不會因?yàn)槎嗑€程的訪問而導(dǎo)致創(chuàng)建多個(gè)實(shí)例允懂,同時(shí)也不會因?yàn)槎嗑€程而引入新的效率問題
1 餓漢式
//原理:通過 JVM 在加載類的時(shí)候來完成靜態(tài)對象的初始化钙畔,而這個(gè)過程本身就是線程安全的(類初始化鎖保證線程安全)千元,無法實(shí)現(xiàn)懶加載哈误,完全依賴虛擬機(jī)加載類的策略進(jìn)行加載
//1.1 靜態(tài)常量
public class Singleton {
//基于 Class Loader 類加載機(jī)制
private final static Singleton INSTANCE = new Singleton();
// 私有構(gòu)造方法暖眼,防止被外部直接實(shí)例化
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
//1.2 靜態(tài)全局變量
public class Singleton {
private static Singleton sInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return sInstance;
}
}
//1.3 靜態(tài)全局變量 靜態(tài)代碼塊
public class Singleton {
private static Singleton sInstance;
static {
sInstance = new Singleton();
}
private Singleton() {}
public Singleton getInstance() {
return sInstance;
}
}
- 通過變通避免了多線程的同步問題
- 延長了類加載的時(shí)間惕耕,效率比較低
- 已經(jīng)加載,如果最終未使用诫肠,就造成了浪費(fèi)資源
2 懶漢式
//2.1 線程不安全
public class Singleton {
private static Singleton sInstance;
private Singleton() {}
/**
*只適合在單線程情況下使用司澎,如果是多線程情況下,一個(gè)線程進(jìn)入 if (singleton == null) 判斷語句塊栋豫,還沒來得及往下執(zhí)行挤安,另一個(gè)線程也通過了這個(gè) if 判斷語句,此時(shí)就會產(chǎn)生多個(gè)實(shí)例
*/
public static Singleton getInstance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}
//2.2 上面的例子 getInstance() 方法加 synchronized 關(guān)鍵字丧鸯,線程安全
public class Singleton {
private static Singleton sInstance;
private Singleton() {}
//對 getInstance() 進(jìn)行了線程同步蛤铜,每次 getInstance 要進(jìn)行同步,大大增加了開銷
public synchronized static Singleton getInstance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}
//2.3 實(shí)例化外面加了 synchronized(Singleton.class) { } 代碼塊丛肢,線程不安全
public class Singleton {
private static Singleton sInstance;
private Singleton() {}
public static Singleton getInstance() {
if (sInstance == null) {
//多個(gè)線程仍舊能夠通過 if 判斷围肥,雖然同步了實(shí)例化的代碼,但還是會多次實(shí)例化
synchronized (Singleton.class) {
sInstance = new Singleton();
}
}
return sInstance;
}
}
//2.4 上面的例子 if 判斷外面加 synchronized(Singleton.class) {} 代碼塊蜂怎,其實(shí)和 2.2 一樣穆刻,線程安全
public class Singleton {
private static Singleton sInstance;
private Singleton() { }
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
return sInstance;
}
}
3 雙重檢查鎖
// Double-Checked Locking 雙重檢查鎖 DCL,進(jìn)行了兩次判空
public class Singleton {
//volatile 關(guān)鍵字聲明杠步,會在編譯時(shí)加 lock氢伟,禁止了指令重排序
private static volatile Singleton sInstance;
private Singleton() {}
public static Singleton getInstance() {
//判斷一次避免不必要的同步鎖
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
線程安全,延遲加載幽歼,效率較高
-
需要使用 volatile 的原因
系統(tǒng)執(zhí)行 sInstance = new Singleton() 這段代碼不是一次性完成的朵锣,大概分為三步指令
1 為需要創(chuàng)建的 Singleton 實(shí)例分配內(nèi)存
2 調(diào)用對應(yīng) Singleton 的構(gòu)造方法
3 將 sInstance 指向前面分配的內(nèi)存空間,此時(shí) sInstance 不為 null 了而指令重排序可能會出現(xiàn)先執(zhí)行 3 再執(zhí)行 2 的情況试躏,所以當(dāng)一個(gè)線程執(zhí)行了 1 和 3 但還沒執(zhí)行 2 (后面走完就會創(chuàng)建一個(gè)實(shí)例)猪勇,而此時(shí)另一個(gè)線程就能通過空判斷而創(chuàng)建了另一個(gè)實(shí)例
4 靜態(tài)內(nèi)部類
//
public class Singleton {
private Singleton() {}
//靜態(tài)內(nèi)部類
private static class SingletonInstanceHolder {
//內(nèi)部類的加載機(jī)制,類的靜態(tài)屬性只會在第一次加載該類的時(shí)候進(jìn)行初始化
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstanceHolder.INSTANCE;
}
}
- 避免了線程不安全颠蕴,延遲加載泣刹,效率高
5 枚舉式
public enum Singleton {
INSTANCE;
public void doSome1(){
}
}
- 避免了線程不安全
- 防止反序列化重新創(chuàng)建新的對象
注意
1 以上方案不考慮反序列化的情況
- 反序列化時(shí)會調(diào)用 readResolve() 方法重新生成一個(gè)實(shí)例助析,所以需要覆寫直接返回單例對象
2 通過用 Map 存值的方式也可以實(shí)現(xiàn)單例的概念
3 Android 有自帶的 Singleton 抽象類
/**
* Singleton helper class for lazily initialization.
*
* Modeled after frameworks/base/include/utils/Singleton.h
*
* @hide
*/
//源碼位置 /frameworks/base/core/java/android/util/Singleton.java
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
思考:在 Java 中構(gòu)造一個(gè)普通對象的成本比較低,那為什么針對單例模式要如此糾結(jié)是否是懶加載呢椅您?
- 加載時(shí):通常單例的生命周期較長外冀,假設(shè)單例類本身在初始化的時(shí)候涉及大量業(yè)務(wù)邏輯,比較復(fù)雜或耗時(shí)掀泳,那是不是不太適合懶加載雪隧,反而需要提前加載呢 ?
- 加載后:假設(shè)單例類初始化后持有了大量資源员舵,如果初始化后卻沒能使用上了就顯得很浪費(fèi)脑沿,那不管用沒用上內(nèi)存的風(fēng)險(xiǎn)一直都存在著,是否就意味著本身就需要進(jìn)行內(nèi)存優(yōu)化了呢马僻?
所以如果把單例類設(shè)計(jì)得比較輕庄拇,是否就不用過多去糾結(jié)懶加載的問題呢?