單例模式 (Singleton
) 是一種建立型模式,指某個類採用Singleton
模式,則在這個類被建立後,只可能產生一個實例供外部訪問,而且提供一個全局的訪問點。java
Java
中單例模式 (Singleton
) 是一種普遍使用的設計模式。單例模式的主要做用是保證在Java
程序中,某個類只有一個實例存在。一些管理器和控制器常被設計成單例模式。編程
簡單點說,就是一個應用程序中,某個類的實例對象只有一個,你沒有辦法去new
,由於構造器是被private
修飾的,通常經過getInstance()
的方法來獲取它們的實例。getInstance()
的返回值是一個同一個對象的引用,並非一個新的實例。單例模式 實現起來也很容易,如下給出六種實現方式:後端
特色:線程安全,沒法實現實例懶加載策略。設計模式
public class Singleton1 {
private final static Singleton1 singleton1 = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance() {
return singleton1;
}
}
複製代碼
特色:線程不安全,實現了實例懶加載策略。緩存
public class Singleton2 {
private final static Singleton2 singleton2;
private Singleton2() {
}
public static Singleton2 getInstance() {
if (singleton2 == null)
singleton2 = new Singleton2();
return singleton2;
}
}
複製代碼
特色:線程安全,且實現了懶加載策略,可是線程同步時效率不高。安全
public class Singleton3 {
private final static Singleton3 singleton3;
private Singleton3() {
}
public synchronized static Singleton3 getInstance() {
if (singleton3 == null)
singleton3 = new Singleton3();
return singleton3;
}
}
複製代碼
特色:線程安全,類主動加載時才初始化實例,實現了懶加載策略,且線程安全。多線程
public class Singleton4 {
private final static Singleton4 singleton4;
private Singleton4() {
}
static {
singleton4 = new Singleton4();
}
public static Singleton4 getInstance() {
return singleton4;
}
}
複製代碼
特色:線程安全,且實現了懶加載策略,同時保證了線程同步時的效率。可是volatile
強制當前線程每次讀操做進行時,保證全部其餘的線程的寫操做已完成。volatile
使得JVM
內部的編譯器捨棄了編譯時優化,對於性能有必定的影響。架構
public class Singleton5 {
private static volatile Singleton5 singleton5;
private Singleton5() {
}
public static Singleton5 getInstance() {
if (singleton5 == null) {
synchronized (Singleton5.class) {
if (singleton5 == null) {
singleton5 = new Singleton5();
}
}
}
return singleton5;
}
}
複製代碼
特色:線程安全,不存在線程同步問題,且單例對象在程序第一次 getInstance()
時主動加載 SingletonHolder
和其 靜態成員 INSTANCE
,於是實現了懶加載策略。框架
public class Singleton6 {
private Singleton6() {
}
private static class SingletonHolder {
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance() {
return Singleton6.SingletonHolder.INSTANCE;
}
}
複製代碼
特色:線程安全,不存在線程同步問題,且單例對象在枚舉類型 INSTANCE
第一次引用時經過枚舉的 構造函數 初始化,於是實現了懶加載策略。異步
public class Singleton7 {
private Singleton7() {
}
enum SingletonEnum {
INSTANCE;
private final Singleton7 singleton7;
private SingletonEnum() {
singleton7 = new Singleton7();
}
}
public static Singleton7 getInstance() {
return SingletonEnum.INSTANCE.singleton7;
}
public static void main(String[] args) {
IntStream.rangeClosed(0, 100).forEach(i -> new Thread() {
public void run() {
out.println(Singleton7.getInstance());
};
}.start());
}
}
複製代碼
這種方式是Effective Java
做者 Josh Bloch
提倡的方式,它不只能避免多線程同步問題,並且還能防止反序列化從新建立新的對象,可謂是很堅強的壁壘啊。不過,因爲JDK 1.5
中才加入enum
特性,用這種方式寫難免讓人感受生疏。
測試代碼以下:
@FixMethodOrder
public class SingletonTester {
protected final static int FROM = 0;
protected final static int TO = 1000;
protected static HashSet<Object> GLOBAL_SET = new HashSet<>();
static {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
out.println();
// count
GLOBAL_SET.forEach((value) -> {
out.println("Global [" + value + "]");
});
}
});
}
// testSingleton1
@Test
public void testSingleton1() throws Exception {
final HashSet<Object> localSet = new HashSet<>();
final CountDownLatch latch = new CountDownLatch(TO);
IntStream.range(FROM, TO).forEach(i -> new Thread() {
public void run() {
Singleton1 singleton = Singleton1.getInstance();
count(singleton);
}
protected void count(Singleton1 singleton) {
localSet.add(singleton);
out.println("Size of HashSet1 is: [" + localSet.size() + "]");
// 計數減1,釋放線程
latch.countDown();
};
}.start());
// 等待子線程執行結束
latch.await();
synchronized (localSet) {
// count
localSet.forEach((value) -> {
out.println("[" + value + "]");
out.println();
});
GLOBAL_SET.addAll(localSet);
}
}
// testSingleton2
// testSingleton3
// testSingleton4
// testSingleton5
// testSingleton6
// testSingleton7
}
複製代碼
測試結果截圖以下,測試用例反映7
種單例模式的方案均可以正常執行:
這裏只演示其中一種單例方式,運行截圖以下:
getInstance()
獲得的實例全局惟一。對於其他六中方式,根據測試用例測試獲得的結果一致,你們能夠自行測試。
本文總結了七種Java
中實現單例模式的方法,其中使用雙重校驗鎖、靜態內部類 和 枚舉類 的方式能夠解決大部分問題。其中,極爲推薦 靜態內部類 和 枚舉類 這兩種實現方式。
歡迎關注技術公衆號: 零壹技術棧
本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。