Java 設計模式 - 單例模式

什麼是單例模式?

單例模式就是說,確保某一個類只有一個實例,並且提供一個全局訪問點。單例模式總體來說就是 3 個特點:

  • 只有一個實例
  • 自我實例化
  • 提供全局訪問點

UML 圖如下,應該很清楚的:

  • 優點

由於只有一個實例,所以能夠節省系統資源,減少性能開銷,提供效率等等,同時也能夠嚴格限制用戶對它的訪問。

  • 缺點

也正因為只有一個實例,也就可能導致單例的職責過重,同時也因為沒有抽象的概念,在擴展上可能也會更困難。

四種實現

關於單例模式有 4 種實現方式,接下來一一來看!

餓漢式

package Hungry;

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return singleton;
    }
}

餓漢式的缺點: 線程安全,調用效率高,但是無法延時加載。

餓漢式一開始就會先把實例 new 好,要用的時候直接返回即可。但是這樣問題也十分明顯,單例還沒使用到時就已經被初始化了,也就是說,即使程序從頭到尾都沒有使用到這個單例,單例還是會被創建,造成資源浪費,比較不推薦這種方式。

懶漢式

package Lazy;

public class Singleton {
    private static Singleton singleton = null;

    private Singleton() {};

    public static Singleton getInstance() {
        if(singleton == null)
            singleton = new Singleton();

        return singleton;
    }

}

上面其實就是單例大致的概念以及實現,但是,這樣的代碼是有瑕疵的,這樣的單例是線程不安全的。來看看測試:

package Lazy;

public class TestLazy extends Thread {
    @Override
    public void run() {
        Singleton lazySingleton = Singleton.getInstance();
    }
}

// 添加於 Singleton class 中
public static void main(String[] args) {
    for(int i = 1; i < 2; i++) {
        Thread thread = new TestLazy();
        thread.start();
    }
}

來看看 debug 的過程以及結果:

可以看到,當第一個線程執行驗證 singleton 是否為 null,準備 new 一個新對象時,第二個線程也驗證完,這時兩個線程都會創建單例,也就表示線程是不安全的。

解決線程不安全的方法有兩種,但大體是差不多的,如下:

Synchronized 實例化加鎖

package Lazy;

public class SynchronizedSingleton {
    private static SynchronizedSingleton singleton = null;

    private SynchronizedSingleton() {};

    public static synchronized SynchronizedSingleton getInstance() {
        if(singleton == null)
            singleton = new SynchronizedSingleton();

        return singleton;
    }
}

加鎖的方法可以達到線程安全,不過對 getInstance 方法加了 staticsynchronized 修飾關鍵詞,可能會對性能有影響。

Double Check + Volatile 雙重檢測

package Lazy;

public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton singleton = null;

    private DoubleCheckSingleton() {};

    public DoubleCheckSingleton getInstance() {
        if(singleton == null) {
            synchronized (DoubleCheckSingleton.class) {
                if(singleton == null)
                    singleton = new DoubleCheckSingleton();
            }
        }

        return singleton;
    }
}

這個方法可以說是很完整的,不僅克服了 JVM 相關問題(這部分個人還不是很懂),也完全的保證的線程安全了。關於 Java 的 volatile 關鍵字可以參考 Java中Volatile关键字详解

靜態內部類式

package StaticInner;

public class Singleton {
    private Singleton() {};

    private static class StaticInnerSingleton {
        private static Singleton singleton = new Singleton();
    }

    public static Singleton getInstance() {
        return StaticInnerSingleton.singleton;
    }
}

使用靜態內部類的優點是,外部類加載時並不需要立即加載內部類,而內部類不加載就不創建實例,也就不占內存。也就是說,當 Singleton 第一次被創建時並不需要加載 StaticInnerSingleton,而是等到調用了 getInstance 方法時才會創建單例。

這樣做不僅可以保證單例的唯一性,又可以提高,還盡可能的推遲了創建實例。

枚舉單例

package Enum;

public enum Singleton {
    singleton;

    public void SingletonOperations() {
        /* 添加操作 */
    }
}

因為枚舉本身就是單例,所以可以這麼實現。關於 Java 的枚舉可以參考 Java枚举,非常清楚。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值