單例設計模式源碼分析,經常使用設計模式白話文總結

什麼是單例

單例模式確保某各種只有一個實例,並且自行實例化並向整個系統提供這個實例。在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具備資源管理器的功能,每臺計算機能夠有若干個打印機,但只能有一個Printer spooler,以免兩個打印做業同時輸出到打印機中,每臺計算機能夠有若干通訊端口,系統應當集中管理這些通訊端口,以免一個通訊端口同時被兩個請求同時調用。總之,選擇單例模式就是爲了不不一致狀態java

單例模式特色

一、單例類只能有一個實例。

二、單例類必須本身建立本身惟一的實例。

三、單例類必須給全部其它對象提供這一實例。

單例模式優缺點

一、單例類只有一個實例
二、共享資源,全局使用
三、節省建立時間,提升性能

單例模式的七種寫法

分別是「餓漢」、「懶漢(非線程安全)」、「懶漢(線程安全)」、「雙重校驗鎖」、「靜態內部類」、「枚舉」和「容器類管理]算法

1.餓漢式

package com.xuyu.V1;

/**
 * author:須臾
 */
public class SingletonV1 {
    /**
     * 餓漢式
     *  優勢:先天線程安全,當類初始化的時候就會建立該對象
     *  缺點:若是餓漢式使用頻繁,可能會影響項目啓動效率
     */
    private static SingletonV1 singletonV1=new SingletonV1();

    /**
     * 將構造函數私有化,禁止初始化
     */
    private SingletonV1(){}

    public static SingletonV1 getInstance(){
        return singletonV1;
    }
    /**
     * 測試單例
     */
    public static void main(String[] args) {
        SingletonV1 instance1 = SingletonV1.getInstance();
        SingletonV1 instance2 = SingletonV1.getInstance();
        //結果爲true,說明保證了單例
        System.out.println(instance1==instance2);

    }
}

源碼分析Runtime設計模式

//餓漢式單例
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }
..
}

2.懶漢式(線程不安全)

package com.xuyu.V2;

/**
 * author:須臾
 */
public class SingletonV2 {

    /**
     * 餓漢式(線程不安全)
     */
    private static SingletonV2 singletonV2;

    private SingletonV2(){}

    /**
     * 建立對象使用
     */
    public static SingletonV2 getInstance(){
        if(singletonV2==null){
            try {
                Thread.sleep(2000);
            }catch (Exception e){
                e.printStackTrace();
            }
            singletonV2=new SingletonV2();
        }
        return singletonV2;
    }

    /**
     * 測試單例
     */
    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            new Thread(new Runnable() {
                public void run() {
                    SingletonV2 instance1 = SingletonV2.getInstance();
                    System.out.println(Thread.currentThread().getName()+","+instance1);
                }
            }).start();
        }
    }
}

輸出結果:線程不安全緩存

Thread-1,com.xuyu.V2.SingletonV2@383a0ba
Thread-4,com.xuyu.V2.SingletonV2@d9d8ad0
Thread-0,com.xuyu.V2.SingletonV2@546431f0
Thread-5,com.xuyu.V2.SingletonV2@2858c11c
Thread-22,com.xuyu.V2.SingletonV2@3635f62a
Thread-6,com.xuyu.V2.SingletonV2@48369750
Thread-7,com.xuyu.V2.SingletonV2@2770f418
Thread-3,com.xuyu.V2.SingletonV2@6d9da26a
Thread-13,com.xuyu.V2.SingletonV2@77355386
Thread-10,com.xuyu.V2.SingletonV2@29580e2d
....
Thread-94,com.xuyu.V2.SingletonV2@3945e031
Thread-91,com.xuyu.V2.SingletonV2@5caf9db6

3.懶漢式(線程安全)

package com.xuyu.V3;

/**
 * author:須臾
 */
public class SingletonV3 {
    /**
     * 懶漢式線程安全
     */
    private static SingletonV3 singletonV3;

    private SingletonV3(){}

    /**
     * 效率低
     */
    public synchronized static SingletonV3 getInstance(){
        try {
            Thread.sleep(2000);
        }catch (Exception e){
            e.printStackTrace();
        }
        if (singletonV3==null){
            System.out.println("建立實例SingletonV3");
            singletonV3=new SingletonV3();
        }
        System.out.println("獲取SingletonV3實例");
        return singletonV3;
    }
    /**
     * 測試單例
     */
    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            new Thread(new Runnable() {
                public void run() {
                    SingletonV3 instance1 = SingletonV3.getInstance();
                    System.out.println(Thread.currentThread().getName()+","+instance1);
                }
            }).start();
        }
    }
}

輸出結果安全

建立實例SingletonV3
獲取SingletonV3實例
Thread-0,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3實例
Thread-99,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3實例
Thread-98,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3實例
Thread-97,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3實例
....

4.雙重檢驗鎖(DCL)

package com.xuyu.V4;

public class SingletonV4 {
    /**
     * volatile 禁止指令重排序
     */
    private static volatile SingletonV4 singletonV4;

    private SingletonV4(){}

    public static SingletonV4 getInstance(){
        if(singletonV4==null){//第一次判斷若是沒有建立對象就開始加鎖
            synchronized (SingletonV4.class){
                if (singletonV4==null){//當用戶搶到鎖,判斷初始化
                    System.out.println("第一次開始建立實例對象,獲取到鎖了");
                    try {
                        Thread.sleep(2000);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    singletonV4=new SingletonV4();
                }
            }
        }
        return singletonV4;
    }
    /**
     * 測試單例
     */
    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            new Thread(new Runnable() {
                public void run() {
                    SingletonV4 instance1 = SingletonV4.getInstance();
                    System.out.println(Thread.currentThread().getName()+","+instance1);
                }
            }).start();
        }
    }
}

輸出結果:線程安全mybatis

第一次開始建立實例對象,獲取到鎖了
Thread-99,com.xuyu.V4.SingletonV4@383a0ba
Thread-89,com.xuyu.V4.SingletonV4@383a0ba
Thread-92,com.xuyu.V4.SingletonV4@383a0ba
Thread-91,com.xuyu.V4.SingletonV4@383a0ba
....
Thread-8,com.xuyu.V4.SingletonV4@383a0ba
Thread-6,com.xuyu.V4.SingletonV4@383a0ba
Thread-9,com.xuyu.V4.SingletonV4@383a0ba
Thread-12,com.xuyu.V4.SingletonV4@383a0ba
Thread-11,com.xuyu.V4.SingletonV4@383a0ba
Thread-10,com.xuyu.V4.SingletonV4@383a0ba
Thread-15,com.xuyu.V4.SingletonV4@383a0ba
Thread-19,com.xuyu.V4.SingletonV4@383a0ba
Thread-16,com.xuyu.V4.SingletonV4@383a0ba

5.靜態內部內形式

package com.xuyu.V5;

/**
 * author:須臾
 */
public class SingletonV5 {
    private SingletonV5(){
        System.out.println("對象初始化...");
    }
    public static SingletonV5 getInstance(){
        return SingletonV5Utils.singletonV5;
    }
    /**
     * 靜態內部類方式:可以避免同步帶來的效率問題和實現懶加載
     */
    public static class SingletonV5Utils{
        private static SingletonV5 singletonV5=new SingletonV5();
    }
    /**
     * 測試單例
     */
    public static void main(String[] args) {
        System.out.println("項目啓動成功。。。");
        SingletonV5 instance1 = SingletonV5.getInstance();
        SingletonV5 instance2 = SingletonV5.getInstance();
        System.out.println(instance1==instance2);

    }
}

輸出結果併發

項目啓動成功。。。
對象初始化...
true

6.枚舉形式

package com.xuyu.V6;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * author:須臾
 */
public enum  EnumSingleton {
    INSTANCE;
    // 枚舉可以絕對有效的防止實例化屢次,和防止反射和序列化破壞
    public void add() {
        System.out.println("add方法...");
    }
    /**
     * 測試單例
     */
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        EnumSingleton instance2 = EnumSingleton.INSTANCE;
        System.out.println(instance1==instance2);
        Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        EnumSingleton v6 = declaredConstructor.newInstance();
        System.out.println(v6==instance1);

    }

}

輸出結果:反射破壞不了單例異步

true
Exception in thread "main" java.lang.NoSuchMethodException: com.xuyu.V6.EnumSingleton.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.xuyu.V6.EnumSingleton.main(EnumSingleton.java:19)

7.使用容器管理

package com.xuyu.V7;

import java.util.HashMap;
import java.util.Map;

public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();
    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }
    public static Object getService(String key) {
        {
            return objMap.get(key);
        }
    }
}

這種使用SingletonManager 將多種單例類統一管理,在使用時根據key獲取對象對應類型的對象。這種方式使得咱們能夠管理多種類型的單例,而且在使用時能夠經過統一的接口進行獲取操做,下降了用戶的使用成本,也對用戶隱藏了具體實現,下降了耦合度。jvm

如何防止破壞單例

雖然單例經過私有構造函數,能夠實現防止程序猿初始化對象,可是還能夠經過反射和序列化技術破壞單例。函數

1.使用反射技術破壞單例

// 1. 使用懶漢式建立對象
SingletonV3 instance1 = SingletonV3.getInstance();
// 2. 使用Java反射技術初始化對象 執行無參構造函數
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);

如何防止被反射破壞

私有構造函數

private SingletonV3() throws Exception {
    synchronized (SingletonV3.class) {
        if (singletonV3 != null) {
            throw new Exception("該對象已經初始化..");
        }
        System.out.println("執行SingletonV3無參構造函數...");
    }

}

2.使用序列化技術破壞單例

Singleton instance = Singleton.getInstance();
FileOutputStream fos = new FileOutputStream("E:\\code\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.flush();
oos.close();

FileInputStream fis = new FileInputStream("E:\\code\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton singleton2 = (Singleton) ois.readObject();
System.out.println(singleton2==instance)

//返回序列化獲取對象 ,保證爲單例
public Object readResolve() {
    return singletonV3;
}

枚舉單例爲何不可以反射初始化

枚舉底層原理

1.首先若是使用java的反射機制破壞單例,報錯

經過該錯誤說明,該枚舉類中無參數構造函數

2.使用java反編譯技術,查看枚舉類

從該圖能夠得出一個結論,枚舉底層其實類。

  1. 枚舉類底層原理分析

使用靜態代碼快方式,當靜態代碼快執行的時候初始化該對象,從而可讓開發者使用經過EnumSingleton.INSTANCE使用。

  1. 在該反編譯源碼中,定義了一個類繼承了Enum  該類是中沒有無參構造函數,因此反射機制調用無參構造函數是沒法初始化的。
  2. 在該類中有一個只有一個有參構造函數

調用父類構造構造函數

Name 爲定義調用對象名稱,定義調用對象序號ordinal

使用注入有參構造函數是否能夠破壞枚舉呢?也不行的。

Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingleton v3 = declaredConstructor.newInstance();
System.out.println(v3 == instance1);

 

爲何報這個錯誤呢?主要緣由是 java的反射初始化對象中,只要對象是是枚舉是不會初始化的的。

總結

到這裏七中寫法都介紹完了,至於選擇用哪一種形式的單例模式,取決於你的項目自己,是不是有複雜的併發環境,仍是須要控制單例對象的資源消耗。

設計模式總結

策略模式:

官方描述(定義一系列算法,把他們封裝起來,而且使它們能夠相互替換)

白話文描述:有共同的抽象行爲,具體不一樣的行爲稱做爲不一樣的策略,最終可使用Context上下文獲取對應策略。

應用場景:解決多重if判斷問題、聚合支付平臺、第三方聯合登錄、調用多個不一樣短信接口等。

責任鏈模式:

官方描述:(將請求的發送者和接收者解耦,使的多個對象都有處理這個請求的機會。)

白話文描述:每個業務模塊之間相互依賴比較有關聯、每一個關聯模塊稱做爲handler(處理器)使用上一個handler引用到下一個hanlder實現一個鏈表。

應用場景: 權限控制、網關權限控制、審批、風控系統等。

模版方法:

官方描述:定義一個算法結構,而將一些步驟延遲到子類實現。

白話文描述:

提早定義好總體的骨架,不一樣的行爲讓子類實現,相同的行爲直接定義在抽象類中複用。

有大致共同抽象行爲所有交給父類實現,不一樣的行爲讓子類實現。

應用場景:支付異步回調重構、Servlet實現

裝飾模式:

官方描述:動態的給對象添加新的功能。

白話文描述:

在不改變原有對象的基礎上附加功能,相比生成子類更靈活。

應用場景:IO流

代理模式:

官方描述:爲其餘對象提供一個代理以便控制這個對象的訪問。

白話文描述:

在方法以前和以後作一些處理 實現AOP通知

應用場景:AOP、事務、日誌、權限控制

觀察者模式:

官方描述: 對象間的一對多的依賴關係。

白話文描述:

在對象之間定義一對多的依賴,這樣一來,當一個對象改變狀態,依賴它的對象收到通知並自動更新
其實就是發佈訂閱模式,發佈者發佈消息,訂閱者獲取消息,訂閱了就能收到消息,沒訂閱就收不到消息。

應用場景: 發佈訂閱 事件通知、 Zookeeper、事件監聽操做

門面模式:

官方描述: 對外提供一個統一的方法,來訪問子系統中的一羣接口。

該模式就是把一些複雜的流程封裝成一個接口供給外部用戶更簡單的使用

狀態模式:

官方描述: 容許一個對象在其對象內部狀態改變時改變它的行爲。

白話文  狀態模式與策略模式本質上沒有很大區別,主要根據行爲決定,若是有共同抽象行爲使用策略模式,沒有共同行爲就使用狀態模式。

適配器模式:
官方描述: 將一個類的方法接口轉換成客戶但願的另一個接口。

應用場景: mybatis日誌收集、提供接口轉換。

單例模式

官方描述:保證在一個jvm中只能有一個實例

本文出自螞蟻課堂:http://www.mayikt.com

相關文章
相關標籤/搜索