死磕設計模式—單例模式
1.1.簡介
例如在Java
開發中,我們都知道類和對象實例可以通過new
來創建一個或者多個,而單例模式就是采取一定的辦法保證整個系統中某一個類只能存在唯一一個對象實例,并且獲取該類實例的方法只能是該類自己提供的一個獲取其實例的靜態方法。
1.2.使用樣例
例如在Spring
源碼中的doGetBean
方法中就使用到了單例模式,如下:
或者JDK
中的RunTime
類就使用到了餓漢式的實現方式創建單例,如下:
[圖片上傳失敗...(image-c02ff2-1564018415917)].png)
單例模式認知小結:
1.一個類只有一個實例;2.類的實例只能是由自己創建;3.該類必須向外提供獲取自己實例的靜態方法;
1.2.應用場景
在《圖解設計模式》一書中詳細介紹了單例模式在開發中的使用場景,如下所示:
一個系統中可以存在多個打印任務,但是只能有一個正在工作的任務;一個系統只能有一個窗口管理器或文件系統;一個系統只能有一個計時工具或ID(序號)生成器
1.2.代碼實現
餓漢式(靜態變量)
- 類構造器私有外(防止外部使用可以
new
一個實例) - 類內部定義一個靜態變量,在類的內部創建該實例
- 提供一個靜態的公共方法返回該類的實例
public class Singleton {
private Singleton() {}
private static final Singleton singleton = new Singleton();
public static Singleton getInstance() {
return singleton;
}
}
優點:代碼實現簡單,在類加載的時候就完成了類的實例化,避免了線程同步問題。
缺點:在類加載的時候完成了實例化,沒有達到懶加載的效果,可能不使用到這個實例,就會造成內存浪費。
餓漢式(靜態代碼塊)
public class Singleton {
private Singleton() {}
private static final Singleton singleton;
static {
singleton = new Singleton();
}
public static Singleton getInstance() {
return singleton;
}
}
使用靜態代碼塊來完成類的實例化和使用靜態變量的優缺點是一樣的。
懶漢式(線程不安全)
public class Singleton {
private Singleton() {}
private static Singleton singleton;
public static Singleton getInstance() {
if (singleton == null) {
return new Singleton();
}
return singleton;
}
}
注意:這種實現方式不推薦使用,雖然達到了懶加載的效果,但是只能在單線程下使用,在多線程下存在線程安全問題。
懶漢式(線程安全,同步方法)
public class Singleton {
private Singleton() {}
private static Singleton singleton;
public static synchronized Singleton getInstance() {
if (singleton == null) {
return new Singleton();
}
return singleton;
}
}
注意:這種實現方法也不推薦使用,雖然解決了線程安全問題,但是使用了
synchronized
這個龐大的東西,使得效率太低太低,每次執行getInstance()
方法,都要進行方法同步。
懶漢式(線程不安全,同步代碼塊)
public class Singleton {
private Singleton() { }
private static Singleton singleton;
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
return new Singleton();
}
}
return singleton;
}
}
注意:同步代碼塊的實現方式是在同步方法上改進的,因為使用同步方法使得效率很低,改為同步代碼塊,但是這種實現方法并不能解決線程安全問題,所以,在實際開發中不推薦使用。
雙重檢查(Double-check,推薦使用)
public class Singleton {
private Singleton() { }
private static volatile Singleton singleton = null;
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
return new Singleton();
}
}
}
return singleton;
}
}
說明:線程安全、延遲加載、效率較高 、推薦使用
實例化代碼只用執行一次,后面再次訪問時,判斷if (singleton == null), 直接return實例化對象,也避免的反復進行方法同步 。
靜態內部類(線程安全,推薦使用)
public class Singleton {
private Singleton() { }
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
- 這種方式采用了類裝載的機制來保證初始化實例時只有一個線程。
- 靜態內部類方式在
Singleton
類被裝載時并不會立即實例化,而是在需要實例化 時,調用getInstance
方法,才會裝載SingletonInstance
類,從而完成Singleton
的 實例化。 - 類的靜態屬性只會在第一次加載類的時候初始化,所以在這里,
JVM
幫助我們保證了線程的安全性,在類進行初始化時,別的線程是無法進入的。 - 避免了線程不安全,利用靜態內部類特點實現延遲加載,效率高 ,推薦使用。
枚舉(推薦使用)
public enum SingletonEnum {
INSTANCE;
public void method() {
}
}
SingletonEnum instance = SingletonEnum.INSTANCE;
說明:不僅能避免多線程同步問題,而 且還能防止反序列化重新創建新的對象 ,缺點:不能懶加載。
1.3.參考文獻
更多文章,更好的閱讀體驗,請前往個人網站查看 碼醬博客