設計模式-第3篇-單例模式

1.單例設計模式的八種方法

  • 餓漢式(靜態常量)
  • 餓漢式(靜態代碼塊)
  • 懶漢式(線程不安全)
  • 懶漢式(線程安全,同步方法)
  • 懶漢式(線程安全,同步代碼塊)
  • 雙重檢查
  • 靜態內部類
  • 枚舉

2.餓漢式(靜態常量)

public class SingletonTest1 {
    public static void main(String[] args) {
        Singleton1 instance1 = Singleton1.getInstance();
        Singleton1 instance2 = Singleton1.getInstance();
        System.out.println(instance1 == instance2); //true
    }
}

/**
 * 餓漢式(靜態變量的方式)
 */
class Singleton1 {
    //私有靜態變量,上來就創建對象
    private final static Singleton1 instance = new Singleton1();

    //私有構造方法
    private Singleton1() {

    }

    //提供公有的供外部訪問的方法
    public static Singleton1 getInstance() {
        return instance;
    }
}

優點:

  • 寫法簡單,類裝載的時候就完成實例化,基於classloader機制避免線程同步問題

缺點:

  • 在類裝載時就實例化,沒有達到lazy loading的效果。如果從始至終未用到這個類,就會造成內存的浪費

總結:

這種單例模式可用,但可能造成內存浪費


3.餓漢式(靜態代碼塊)

public class SingletonTest2 {
    public static void main(String[] args) {
        Singleton2 instance1 = Singleton2.getInstance();
        Singleton2 instance2 = Singleton2.getInstance();
        System.out.println(instance1 == instance2); //true
    }
}

/**
 * 餓漢式(靜態代碼塊的方式)
 */
class Singleton2 {
    //私有靜態變量,僅做定義
    private static Singleton2 instance;

    //靜態代碼塊中實例化
    static {
        instance = new Singleton2();
    }

    //私有構造方法
    private Singleton2() {

    }

    //提供公有的供外部訪問的方法
    public static Singleton2 getInstance() {
        return instance;
    }
}

4.懶漢式(線程不安全-單線程可用)

public class SingletonTest {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
    }
}

/**
 * 餓漢式
 */
class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        //爲空時才創建對象,起到懶加載的效果
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

優點:

起來懶加載的效果

缺點:

只能在單線程下使用。

原因如下:

如果在多線程下,一個線程進入了if (instance == null)判斷語句塊,還未來得及向下執行,另一個線程也通過了這個判斷語句,這時就會產生多個實例,所以在多線程下不可使用此種方式

驗證demo

public class SingletonTest {
    public static void main(String[] args) {
//        Singleton instance1 = Singleton.getInstance();
//        Singleton instance2 = Singleton.getInstance();
//        System.out.println(instance1 == instance2);

        new Thread(() ->{
            try {
                Singleton instance3 = Singleton.getInstance();
                System.out.println(Thread.currentThread().getName()+":hashcode-"+instance3.hashCode());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() ->{
            try {
                Singleton instance3 = Singleton.getInstance();
                System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

/**
 * 餓漢式
 */
class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() throws InterruptedException {
        //爲空時才創建對象,起到懶加載的效果
        if (instance == null) {
            Thread.sleep(1000);
            instance = new Singleton();
        }
        return instance;
    }

}

在這裏插入圖片描述

總結:

不要採用,會出現線程安全問題


5.懶漢式(線程安全,同步方法)

實現方式僅僅是在public static Singleton getInstance()上增加一個synchronized關鍵字

public class SingletonTest {
    public static void main(String[] args) throws InterruptedException {
//        Singleton instance1 = Singleton.getInstance();
//        Singleton instance2 = Singleton.getInstance();
//        System.out.println(instance1 == instance2);

        new Thread(() ->{
            try {
                Singleton instance3 = Singleton.getInstance();
                System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() ->{
            try {
                Singleton instance3 = Singleton.getInstance();
                System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

/**
 * 餓漢式-獲取對象實例的方法加鎖
 */
class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() throws InterruptedException {
        //爲空時才創建對象,起到懶加載的效果
        if (instance == null) {
            Thread.sleep(1000);
            instance = new Singleton();
        }
        return instance;
    }

}
  • 解決了線程不安全的問題
  • 效率太低了。每個線程在獲得類的實例時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次實例化代碼就夠了,後面的想獲得該類實例,直接return就行了。方法進行同步效率太低。
  • 實際開發不推薦

6.懶漢式(線程不安全,同步代碼塊)

public class SingletonTest {
    public static void main(String[] args) throws InterruptedException {
//        Singleton instance1 = Singleton.getInstance();
//        Singleton instance2 = Singleton.getInstance();
//        System.out.println(instance1 == instance2);

        new Thread(() ->{
            try {
                Singleton instance3 = Singleton.getInstance();
                System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() ->{
            try {
                Singleton instance3 = Singleton.getInstance();
                System.out.println(Thread.currentThread().getName()+":"+instance3.hashCode());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

/**
 * 餓漢式
 */
class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() throws InterruptedException {
        //爲空時才創建對象,起到懶加載的效果
        if (instance == null) {
            synchronized (Singleton.class){
                Thread.sleep(1000);
                instance = new Singleton();
            }
        }
        return instance;
    }

}

並不能解決線程安全問題

在這裏插入圖片描述


7.雙重檢查(線程安全並推薦)

/**
 * 餓漢式-雙重檢查優化同步代碼塊線程不安全的問題
 */
class Singleton {

    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() throws InterruptedException {
        //爲空時才創建對象,起到懶加載的效果
        if (instance == null) {
            //爲null時才加鎖,保證只有一條線程創建對象
            synchronized (Singleton.class) {
                if (instance == null) {
                    Thread.sleep(1000);
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

兩次進行if 檢查,保證線程安全。這樣實例化代碼只用執行一次,並且保證只能由一個線程執行,後面再次訪問是,再次判斷if是否爲nul。直接return實例化對象,避免了反覆進行方法同步。

  • 線程安全,延遲加載,效率較高
  • 實際開發推薦

8.靜態內部類(推薦)

特點是,外部類裝載時內部類不會被裝載,只有在調用內部類的相關方法和屬性時,纔會被裝載,並且只被裝載一次

/**
 * 靜態內部類
 */
class Singleton {

    // 定義私有的靜態內部類
    private static class SingletonInnerInstance {
        private static final Singleton SINGLETON_INSTANCE = new Singleton();
    }

    private Singleton() {
    }

    // 返回靜態內部生成的外部類對象
    public static Singleton getInstance() {
        return SingletonInnerInstance.SINGLETON_INSTANCE;
    }

}
  • 這種方式採用了類裝載的機制來保證初始化實例時只有一個線程

    在這裏插入圖片描述


9.枚舉(推薦)

public class SingletonTest {
    public static void main(String[] args) {
        SingletonEnum instance1 = SingletonEnum.INSTANCE;
        SingletonEnum instance2 = SingletonEnum.INSTANCE;
        System.out.println(instance1 == instance2);
    }
}

/**
 * 利用枚舉實現單例
 */
enum SingletonEnum {
    INSTANCE;

    public void method1() {
        System.out.println("OK...");
    }
}
  • 避免多線程同步問題
  • 防止反序列化重新創建新的對象
  • Effective Java提倡。推薦使用

10.JDK源碼分析

在這裏插入圖片描述


11.單例模式-總結

  • 單例節省了系統資源,對於一些需要頻繁創建和銷燬的對象,使用單例模式可以提高系統性能
  • 當想實例化一個單例類的時候,必須要記住使用響應的獲取對象的方法,而不是直接new
  • 使用場景:
    • 需要頻繁創建和銷燬的對象
    • 創建對象時耗時過多或耗費資源過多(重量級對象),但又經常用到的對象、工具類對象、頻繁訪問數據庫或文件的對象(比如數據源、session工廠等)

12.個人應用實戰

將工具類修改爲單例模式-------

使用雙重檢查、靜態內部類都可以

要改爲單例的工具類最好是那種類似數據庫連接池工具類那種,涉及到配置、可擴展那種。否則不如使用靜態方法的方式


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