單例模式 定義 保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。 通常我們可以讓一個全局變數使得一個對象被訪問,但它不能防止你實例化多個對象。一個最好的辦法就是,讓類自身負責保存它的唯一實例。這個類可以保證沒有其他實例可以創建,並且它可以提供一個訪問該實例的方法。 UML圖 方式一:單線程下的 ...
單例模式
定義
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
通常我們可以讓一個全局變數使得一個對象被訪問,但它不能防止你實例化多個對象。一個最好的辦法就是,讓類自身負責保存它的唯一實例。這個類可以保證沒有其他實例可以創建,並且它可以提供一個訪問該實例的方法。
UML圖
方式一:單線程下的單例
/**
* Created by callmeDevil on 2019/8/17.
*/
public class Singleton {
private static Singleton instance;
private Singleton(){} //構造方法私有,防止外界創建實例
// 獲得本類實例的唯一全局訪問點
public static Singleton getInstance(){
if (instance == null) {
//若實例不存在,則創建一個新實例,否則直接返回已有實例
instance = new Singleton();
}
return instance;
}
}
測試
public class Test {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if (s1 == s2) {
System.out.println("兩個對象是相同的實例");
}
}
}
測試結果
兩個對象是相同的實例
在沒有併發問題的情況下,這種方式也是使用比較多的。但缺點也很明顯,多線程下根本沒法用。
方式二:多線程下的單例
/**
* Created by callmeDevil on 2019/8/17.
*/
public class SingletonOnLock {
private static SingletonOnLock instance;
private SingletonOnLock(){}
public static SingletonOnLock getInstance(){
// 同步代碼塊,只有一個線程能進入,其他阻塞
synchronized (SingletonOnLock.class){
if(instance == null){
instance = new SingletonOnLock();
}
}
return instance;
}
}
存在問題
當存在對象實例時,完全不用擔心併發時導致堆中創建多個實例,但每次調用 getInstance() 方法時都被加鎖,是會影響性能的,因此這個類可以繼續改良。
方式三:雙重鎖定(DCL)
/**
* Created by callmeDevil on 2019/8/17.
*/
public class SingletonOnDoubleCheckLock {
private static SingletonOnDoubleCheckLock instance;
private SingletonOnDoubleCheckLock(){}
public static SingletonOnDoubleCheckLock getInstance(){
// 先判斷實例是否存在,不存在再考慮併發問題
if (instance == null) {
synchronized (SingletonOnDoubleCheckLock.class){
if(instance == null){
instance = new SingletonOnDoubleCheckLock();
}
}
}
return instance;
}
}
兩次判斷實例是否存在的原因
當實例存在時,就直接返回,這是沒有問題的。當實例為空並且有兩個線程調用 getInstance() 方法時,它們都可以通過第一重 instace == null 的判斷,然後由於 synchronized 機制,只有一個線程可以進入,另一個阻塞,必須要在同步代碼塊中的線程出來後,另一個線程才會進入。而此時如果沒有第二重的判斷,那第二個線程仍然會創建實例,這就達不到單例的目的了。
但這種方式是最讓人“詬病”的一種不推薦方式,技巧看上去很好,但實際上同樣影響性能。
方式四:靜態初始化
/**
* 該類聲明為final ,阻止派生,因為派生可能會增加實例
* Created by callmeDevil on 2019/8/17.
*/
public final class SingletonStatic {
// 第一次引用類的任何成員時就創建好實例,同時沒有併發問題
private static final SingletonStatic instance = new SingletonStatic();
private SingletonStatic(){}
public static SingletonStatic getInstance(){
return instance;
}
}
JVM第一次載入類的時候就已經創建好了實例,如果接下來的很長時間都沒有用到的話,占用的記憶體相當於被浪費了,也不是最讓人推薦的一種方式。當然現在的伺服器容量也越來越大,單單一個實例的記憶體也並不是任何情況都要考慮節省。除非追求極致。。
總結
- 靜態初始化的方式是自己被載入時就已經將自己實例化,因此也被稱為“餓漢式”。
- 其他方式是要在第一次被引用時,才會將自己實例化,所以被稱為“懶漢式”。
- 其實單例模式還有很多種實現方式,下麵再提一種《大話設計模式》中未提到的實現:靜態內部類。 這是樓主在《Java併發編程實戰》中看到的,也是最為推薦的一種。
擴展
方式五:靜態內部類
/**
* Created by callmeDevil on 2019/8/17.
*/
public class SingletonStaticClass {
private SingletonStaticClass() {}
public SingletonStaticClass getInstande() {
return InterClass.instance;
}
// 靜態內部類,沒有併發問題
private static final class InterClass {
public static SingletonStaticClass instance = new SingletonStaticClass();
}
}
推薦原因
JVM第一次載入外部的 SingletonStaticClass 時,並不會直接實例化,所以這種方式也屬於“懶漢式”。只有在第一次調用 getInstance() 方法時,JVM才會載入內部類 InterClass,接著才實例化靜態變數,也就是我們需要的外部類的單例。這樣不僅延時了實例化,同時也解決了併發訪問的問題,因此該方式是最為推薦的一種方式。