Java設計模式的單例模式Singleton Pattern,屬於建構式模式(Creational Patterns)的一種,是常見也是面試時常考的基本設計模式之一。
單例(Singleton)的意思就是只有一個實例。概念同static,是一種全域的表現。 在Java OOP中一個類別(Class)可以產生多個實例(Instance),這些實例彼此是不同的。而單例就是希望應用程式的生命週期中只會有一個實例。
public class Main {
public static void main(String[] args) {
Main main1 = new Main(); // 產生一個實例
Main main2 = new Main(); // 產生另外一個實例
System.out.println(main1 == main2); //false
}
}
如果整個應用程式只需要一個東西,那就不用重複產生一模一樣的東西。單例常見的應用場景為:
- 連線物件
- 配置物件
- 日誌物件
單例模式簡單實作如下
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
在程式中要使用單例類別的物件時,並不直接new
新的物件,而是透過公開的靜態方法取得,也就是上面的getInstance()
。產生的實例會被放入類別的靜態成員變數中,而靜態成員變數的記憶體位置是固定的,所以之後的程式再透過getInstance()
取得實例時,會先判斷之前靜態成員變數中是否存在實例,若不存在則新增一個實例;若存在代則返回的先前產生的實例,而非另外產生一個新的。因為每次都是使用同一個的實例,所以稱作"單例"模式。
以上是非執行緒安全(non-thread-safe)的單例。在多執行緒的程式中,則設計要改為執行緒安全(thread-safe)的單例模式以避免執行緒干涉(thread interference)的問題。
通常碰到執行緒同步的問題會直覺想到用synchronized
來確保邏輯的同步,例如直接將取得實例的getInstance()
方法改為同步。
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance = null;
private ThreadSafeSingleton() {
}
public synchronized static ThreadSafeSingleton getInstance() {
if(instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}
但此做法的問題是效率差。每次取得單例時都要進入同步方法,而同步取得鎖的成本較高。此外一旦單例物件生成後就沒有必要同步。
因此把同步的範圍縮小到產生實例的部分。當實例已存在直些返回不用進同步,只有生成時才需要同步,此時要利用double check locking的技巧。
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance = null;
private ThreadSafeSingleton() {
}
public static ThreadSafeSingleton getInstance() {
if(instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
利用執行緒的Class鎖(class level lock),也就是synchronized (ThreadSafeSingleton.class)
區塊來限制多執行緒中只允許一個實例存取。
以上的好處是只有第一次產生單例時才進入同步,比起直接在getInstance()
方法做同步的效能較好。
或是Class loader載入時直接產生實例。但這樣就沒有懶初始化(lazy-initialization)的效果。
public class ThreadSafeSingleton {
private static final ThreadSafeSingleton INSTANCE = new ThreadSafeSingleton();
private ThreadSafeSingleton() {
}
public static ThreadSafeSingleton getInstance() {
return INSTANCE;
}
}
在Java的Runtime
有相同的實現。
java.lang.Runtime
public class Runtime {
private static final Runtime currentRuntime = new Runtime();
...
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {
}
...
}
或是用一個內部靜態類來達到懶初始化的效果,又稱為Bill Pugh Singleton。
public class ThreadSafeSingleton {
private ThreadSafeSingleton() {
}
public static ThreadSafeSingleton getInstance() {
return ThreadSafeSingletonHolder.INSTANCE;
}
private static class ThreadSafeSingletonHolder {
static final ThreadSafeSingleton INSTANCE = new ThreadSafeSingleton(); // 只有當被參考時才會初始化
}
}
而以上單例的狀態都可能被反射及序列化反序列化破壞。在Effective Java書中建議使用列舉(enum)來保證單例。Enum可確保執行緒安全,單例不會被反射及序列化反序列化破壞。
public enum EnumSingleton {
INSTANCE;
private int value;
public void doSomething() {
// ...
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
Spring Bean scope預設為singleton,也就是註冊的Bean在Spring IoC容器中只會有一個bean。 但同個類別仍可註冊多個Bean,這和Singleton還是有點差異。
參考:
沒有留言:
張貼留言