1. 程式人生 > 其它 >設計模式輕鬆學之01 單例模式

設計模式輕鬆學之01 單例模式

技術標籤:設計模式輕鬆學 Java版java設計模式

設計模式輕鬆學之01 單例模式

前言

GOF( Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides)聯合出版的書中提到的設計模式一共有23種,可以分為三大類:建立型模式(Creational Patterns)、結構型模式(Structural Patterns)、行為型模式(Behavioral Patterns)。

單例模式的特點

這一章我們主要介紹建立型模式之單例模式(Singleton Pattern)。它的特點是,保證系統中某個類只有一個例項,並且提供一個全域性訪問點。在我們開發的系統中ServletContext和Spring框架的ApplicationContext都是單例的,資料庫連線池也是單例的。

建立單例物件要保證兩點:

1. 構造器私有化

這樣其他人就無法通過new關鍵字來生成另一個物件。

2. 提供一個全域性訪問點

通常提供一個使用public static 來修飾的getInstance方法。

兩種單例模式的特點

單例模式主要分為兩種,惡漢式和懶漢式。

1. 惡漢式

惡漢式指的是類在被載入的時候就會生成例項,雖然可能會浪費一些空間,但是效能比較好,絕對的執行緒安全,而且寫法簡單,如果沒有特殊要求的話推薦使用這種方法。

2.懶漢式

而懶漢式單例需要在呼叫的時候才建立,這種情況導致了可能會出現執行緒安全的危險,所以處理起來會比較的麻煩,而且效率也會受到影響。

惡漢式單例詳解

第一種寫法

惡漢式的寫法非常簡單:

public class HungrySingleton {
    // 構造器私有化
    private HungrySingleton(){}
    // 內部構造例項
    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
    // 全域性訪問點
    public HungrySingleton getInstance
() { return HUNGRY_SINGLETON; } // 其他業務程式碼... }

第二種寫法

還有一個變種的寫法是將內部構造的例項放到靜態程式碼塊中來處理,但它們的原理是一樣的:

public class HungrySingleton {
    // 內部構造例項
    private static final HungrySingleton HUNGRY_SINGLETON;
    static {
        HUNGRY_SINGLETON = new HungrySingleton();
    }
    // 構造器私有化
    private HungrySingleton(){}
    // 全域性訪問點
    public HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
    // 其他業務程式碼...
}

懶漢式單例詳解

有問題的寫法

首先我們用最簡單的方式來寫一個懶漢式單例:

public class LazySingleton {
    // 構造器私有化
    private LazySingleton(){}
    // 內部類例項
    private static LazySingleton lazySingleton;
    // 全域性訪問點
    public static LazySingleton getInstance() {
        // 如果內部類例項沒有被初始化,那麼進行初始化
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
    // 其他業務程式碼...
}

使用synchronized關鍵字加鎖

以上寫法有一個問題,那就是如果多個執行緒同時進入if判斷語句中,就會多次執行lazySingleton = new LazySingleton()這條語句。為了解決這個問題,需要在getInstance方法上加鎖:

public class LazySingleton {
    // 構造器私有化
    private LazySingleton(){}
    // 內部類例項
    private static LazySingleton lazySingleton;
    // 全域性訪問點
    // **使用synchronized加鎖**
    public synchronized static LazySingleton getInstance() {
        // 如果內部類例項沒有被初始化,那麼進行初始化
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
    // 其他業務程式碼...
}

雙重檢查鎖

但是,用 synchronized 加鎖,線上程數量比較多情況下, CPU 分配壓力上升,會導致大批量執行緒出現阻塞,從而導致程式執行效能大幅下降。為了兼顧執行緒安全又提升程式效能,我們引入了雙重檢查鎖的單例模式:

public class LazySingleton {
    // 構造器私有化
    private LazySingleton(){}
    // 內部類例項
    private static LazySingleton lazySingleton;
    // 全域性訪問點
    // **不在getInstance上加鎖了**
    public  static LazySingleton getInstance() {
        // 判斷內部類例項是否為空
        if (lazySingleton == null) {
            // 在if內部加鎖
            synchronized (LazySingleton.class) {
                // 第二次判斷是否為空
                if (lazySingleton == null) {
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
    // 其他業務程式碼...
}

雙重檢查鎖也有可能會被阻塞,但是阻塞的地方是在getInstance方法的內部,而第一種鎖阻塞的是整個類的阻塞,因此效能還是有不少的提升的。

內部靜態類

那麼單例模式還有沒有更好的不用加鎖的寫法呢?還是有的,那就是使用內部靜態類的方式:

public class LazyInnerClassSingleton {
    // 構造器私有化
    private LazyInnerClassSingleton(){}

    // 全域性訪問點
    public static final LazyInnerClassSingleton getInstance() {
        return InstantHolder.LAZY_INNER_CLASS_SINGLETON;
    }
    // 額外的內部類
    private static class InstantHolder{
        // 在內部類中初始化例項物件
        private static final LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = new LazyInnerClassSingleton();
    }

    // 其他業務程式碼...
}

單例模式小結

以上就是單例模式的全部內容,其實還算是比較簡單的,因為它的功能其實也比較單一。我們在開發的時候通常會使用Spring IOC容器,通常可以通過配置來實現單例模式的目的。但是單例模式可以算是面試中比較多的一個問題,瞭解其中原理也有助於提高我們的核心競爭力。