Java單例模式

昨天讀到了公眾號“Import New”的Hi,我們再來聊一聊 Java 的單例 很有收獲,在此做個簡單的記錄。

1.懶漢式

1.1簡單式

// Version 1
public class Single1 {
    private static Single1 instance;
    public static Single1 getInstance() {
        if (instance == null) {
            instance = new Single1();
        }
        return instance;
    }
}
// Version 1.1
public class Single1 {
    private static Single1 instance;
    private Single1() {}
    public static Single1 getInstance() {
        if (instance == null) {
            instance = new Single1();
        }
        return instance;
    }
}

問題:多個線程同時訪問,如果有多個線程同時運行到if (instance == null)時,都判斷為null,這個時候就不是單例了。
想法:加上synchronized同步鎖

1.2synchronized版本

// Version 2 
public class Single2 {
    private static Single2 instance;
    private Single2() {}
    public static synchronized Single2 getInstance() {
        if (instance == null) {
            instance = new Single2();
        }
        return instance;
    }
}

問題:給gitInstance方法加鎖,雖然會避免了可能會出現的多個實例問題,但是會強制除T1之外的所有線程等待,實際上會對程序的執行效率造成負面影響。
想法:double-check

1.3 double-chek版本

// Version 3 
public class Single3 {
    private static Single3 instance;
    private Single3() {}
    public static Single3 getInstance() {
        if (instance == null) {
            synchronized (Single3.class) {
                if (instance == null) {
                    instance = new Single3();
                }
            }
        }
        return instance;
    }
}

第一個if (instance == null)是為了解決上一方案中的效率問題
第二個if (instance == null)是為了防止多個實例
問題:1、instance = new Single3();非原子操作 2、會受到指令重排的影響。

原子操作:簡單來說,原子操作(atomic)就是不可分割的操作,在計算機中,就是指不會因為線程調度被打斷的操作。
例如:賦值操作

m = 6;

指令重排:簡單來說,就是計算機為了提高執行效率,會做的一些優化,在不影響最終結果的情況下,可能會對一些語句的執行順序進行調整。

下面這段話直接從陳皓的文章(深入淺出單實例SINGLETON設計模式)中復制而來:

主要在于singleton = new Singleton()這句,這并非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。

  1. 給 singleton 分配內存

  2. 調用 Singleton 的構造函數來初始化成員變量,形成實例

  3. 將singleton對象指向分配的內存空間(執行完這步 singleton才是非 null 了)
    但是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執行完畢、2 未執行之前,被線程二搶占了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然后使用,然后順理成章地報錯。

再稍微解釋一下,就是說,由于有一個『instance已經不為null但是仍沒有完成初始化』的中間狀態,而這個時候,如果有其他線程剛好運行到第一層if (instance == null)這里,這里讀取到的instance已經不為null了,所以就直接把這個中間狀態的instance拿去用了,就會產生問題。

這里的關鍵在于——線程T1對instance的寫操作沒有完成,線程T2就執行了讀操作。

1.4 終極版本:volatile

// Version 4 
public class Single4 {
    private static volatile Single4 instance;
    private Single4() {}
    public static Single4 getInstance() {
        if (instance == null) {
            synchronized (Single4.class) {
                if (instance == null) {
                    instance = new Single4();
                }
            }
        }
        return instance;
    }
}

volatile關鍵字的一個作用是禁止指令重排,把instance聲明為volatile之后,對它的寫操作就會有一個內存屏障(什么是內存屏障?),這樣,在它的賦值完成之前,就不用會調用讀操作。

注意:volatile阻止的不是singleton = new Singleton()這句話內部[1-2-3]的指令重排,而是保證了在一個寫操作([1-2-3])完成之前,不會調用讀操作(if (instance == null))。

2.餓漢式單例

餓漢式單例是指:指全局的單例實例在類裝載時構建的實現方式。

2.1 餓漢式單例的實現方式

//餓漢式實現
public class SingleB {
    private static final SingleB INSTANCE = new SingleB();
    private SingleB() {}
    public static SingleB getInstance() {
        return INSTANCE;
    }
}

問題:INSTANCE的初始化是在類加載時進行的,而類的加載是由ClassLoader來做的,所以開發者本來對于它初始化的時機就很難去準確把握。

3 其他的一些方式

3.1 Effective Java 1 —— 靜態內部類

// Effective Java 第一版推薦寫法
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

3.2 Effective Java 2 —— 枚舉

// Effective Java 第二版推薦寫法
public enum SingleInstance {
    INSTANCE;
    public void fun1() { 
        // do something
    }
}
 
// 使用
SingleInstance.INSTANCE.fun1();

4.可參考的鏈接

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 主要參考自 菜鳥教程 單例模式是JAVA中最簡單的模式之一,這種模式屬于創建型模式,它提供了一種創建對象的最...
    東溪95閱讀 570評論 0 2
  • 單例的定義 確保某一個類只有一個實例,而且自行實例化并且向整個系統提供整個實例。 使用場景 確保一個類有且只有一個...
    Tyhj閱讀 153評論 0 0
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,366評論 11 349
  • 1. 實現單例模式 餓漢模式和懶漢模式單例模式根據實例化時機分為餓漢模式和懶漢模式。餓漢模式,是指不等到單例真正使...
    aaron1993閱讀 228評論 0 0
  • 今天是5.20,有些人為了今天準備了很久,有些小清新的約會,又或是浪漫的告白。女孩,原諒我在這里用這個你看不見的方...
    阿錦男孩閱讀 524評論 0 0