單例模式與多線程(一)

在標準的23個設計模式中,單例設計模式在應用中是比較常見的。但在常規的該模式教學資料介紹中,多數沒有結合多線程技術作爲參考,這就造成在使用多線程技術的單例模式時會出現一些意想不到的情況,這樣的代碼如果在生產環境中出現異常,有可能造成災難性的後果。
在學習多線程技術的單例模式時,只需要考慮一件事,那就是:如何使單例模式遇到多線程是安全的、正確的。下面就不同方式實現多線程中的單例進行介紹:
一、“餓漢模式”實現多線程中的單例
下面通過示例演示:

package com.javapatterns.singleton;
/**
 * 測試餓漢模式在多線程環境下的單例
 */
public class Test {
    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        Thread t3 = new Thread(myRunnable);

        t1.start();
        t2.start();
        t3.start();
    }
}

class Admin {

    // 提供一個當前類型的靜態變量,靜態變量在類加載時創建,而且只執行一次
    private static Admin a = new Admin();

    // 構造方法私有化
    private Admin() {

    }

    /*
     * 餓漢模式的缺點是不能有其他的實例變量 因爲getInstance()方法沒有同步,所以有可能會出現非線程安全問題,
     * 解決方法是在getInstance()方法前加關鍵字synchronized使其同步
     */
    // 提供一個公開的靜態的獲取當前類型對象的方法
    synchronized public static Admin getInstance() {
        return a;
    }
}

/*
 * 通過實現Runnable接口實現多線程
 */
class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Admin.getInstance().hashCode());

    }

}

運行結果如圖 3-1所示:
這裏寫圖片描述
圖 3-1 “餓漢模式”結合多線程的運行結果

由圖可知,控制檯打印的hasCode是同一個值,說明對象是同一個,也就說明用“餓漢模式”實現了多線程中的單例。


二、“懶漢模式”實現多線程中的單例
“懶漢模式”實現多線程中的單例有多種方式:

  1. 聲明synchronized關鍵字實現
  2. 通過同步代碼塊實現
  3. 使用DCL雙檢查鎖機制實現

由於前兩種方法的實現運行效率較低,所以只給出關鍵代碼,不做詳細介紹,下面會針對第三種實現方法展開介紹。
前兩種實現方法的關鍵代碼:

/*
     * 第一種方法
     * 通過聲明synchronized關鍵字實現
     * 要領:在getInstance()方法前加關鍵字synchronized
     */
    synchronized public static Singleton getInstance() {
        if (s == null) {
            s = new Singleton();
        }
        return s;
    }
/*   * 第二種方法
     * 通過同步代碼塊實現
     * 要領:在getInstance()方法裏的代碼上對對應的Class類進行同步
     * 注意:同步synchronized(class)代碼塊的作用其實和synchronized static方法的作用一樣。
     * 
     */
     public static Singleton getInstance() {
         synchronized(Singleton.class) {
             if (s == null) {
                    s = new Singleton();
                }
                return s;
         }

    }

通過使用DCL雙檢查鎖機制實現的示例如下:

package com.javapatterns.singleton;

/**
 * 測試DCL雙檢查鎖機制在多線程環境下的單例
 */
public class Test {
    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        Thread t3 = new Thread(myRunnable);

        t1.start();
        t2.start();
        t3.start();
    }
}

class Singleton {
    // 提供一個當前類型的靜態變量
    private static Singleton s;

    // 構造方法私有化
    private Singleton() {
    }

    // 提供一個公開的靜態的獲取當前類型對象的方法
    /*
     * 使用雙檢測機制來解決問題,既保證了不需要同步代碼的異步執行 又保證了單例的效果
     * 
     */
    public static Singleton getInstance() {

        try {
            // 第一次檢查
            if (s != null) {

            } else {
                // 模擬在創建對象之前做一些準備性工作
                Thread.sleep(3000);
                // 通過同步synchronized(class)代碼塊對對應的類進行加鎖
                synchronized (Singleton.class) {
                    // 第二次檢查
                    if (s == null) {
                        s = new Singleton();
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return s;
    }

}

/*
 * 通過實現Runnable接口實現多線程
 */
class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Admin.getInstance().hashCode());

    }

}

運行結果如圖 3-2所示
這裏寫圖片描述
圖 3-2 “懶漢模式”結合多線程的運行結果

由圖可知,控制檯打印的hasCode是同一個值,說明對象是同一個,也就說明用“懶漢模式”實現了多線程中的單例。
使用雙檢查鎖功能,成功地解決了“懶漢模式”遇到多線程的問題。DCL也是大多數多線程結合單例模式使用的解決方案。


三、使用靜態內部類實現多線程中的單例
下面通過示例演示:

package com.javapatterns.singleton;

/**
 * 測試使用靜態內部類實現在多線程環境下的單例
 */
public class Test {

    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        Thread t3 = new Thread(myRunnable);

        t1.start();
        t2.start();
        t3.start();
    }
}

class Singleton {
    //靜態內部類
    private static class InnerClass {
        // 提供一個當前類型的靜態變量,靜態變量在類加載時創建,而且只執行一次
        private static Singleton s = new Singleton();
    }

    // 構造方法私有化
    private Singleton() {
    }

    // 提供一個公開的靜態的獲取當前類型對象的方法

    public static Singleton getInstance() {
        // 返回內部類裏的當前類型的靜態變量
        return InnerClass.s;
    }

}
/*
 * 通過實現Runnable接口實現多線程
 */
class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Admin.getInstance().hashCode());

    }

}

運行結果如圖 3-3所示
這裏寫圖片描述
圖 3-3 靜態內部類實現的單例模式結合多線程的運行結果

由圖可知,控制檯打印的hasCode是同一個值,說明對象是同一個,也就說明用 靜態內部類實現的單例模式實現了多線程中的單例。


四、使用static代碼塊實現多線程中的單例
靜態代碼塊中的代碼在使用類的時候就已經執行了,所以可以應用靜態代碼塊的這個特性來實現多線程中的單例。
下面通過示例演示:

package com.javapatterns.singleton;

/**
 * 測試static代碼塊實現的單例模式在多線程環境下的單例
 */
public class Test {

    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        Thread t3 = new Thread(myRunnable);

        t1.start();
        t2.start();
        t3.start();
    }
}

class Singleton {

    // 提供一個當前類型的靜態變量
    private static Singleton s;

    // 構造方法私有化
    private Singleton() {
    }

    // 在靜態代碼塊中創建該類型的對象,因爲在靜態代碼塊中,所以只被執行一次,而且是在類被載入時執行
    static {
        s = new Singleton();
    }

    // 提供一個公開的靜態的獲取當前類型對象的方法
    public static Singleton getInstance() {
        return s;
    }

}

/*
 * 通過實現Runnable接口實現多線程
 */
class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Admin.getInstance().hashCode());

    }

}

未完,待續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章