單例模式

一般實現(xiàn)單例模式的幾種思路
懶漢和餓漢

  • 餓漢式:在類加載時就完成了初始化,所以類加載比較慢,但獲取對象的速度快。
  • 懶漢式:在類加載時不初始化,等到第一次被使用時才初始化。

1.餓漢式

package com.d4c.example;

/**
 * 餓漢式
 */
public class SingletonHungry {
    private final static SingletonHungry SINGLETON_HUNGRY = new SingletonHungry();

    private SingletonHungry() {
    }

    public static SingletonHungry getInstance() {
        return SINGLETON_HUNGRY;
    }
}


  • 優(yōu)點:在類加載的時候就完成了實例化,避免了多線程的同步問題。
  • 缺點:因為類加載時就實例化了,沒有達到Lazy Loading (懶加載) 的效果,如果該實例沒被使用,內(nèi)存就浪費了。

2.懶漢式(同步方法)

package com.d4c.example;

/**
 * 懶漢式
 */
public class SingletonLazy {
    
    private static SingletonLazy singletonLazy = null;

    private SingletonLazy() {
    }

    public static synchronized SingletonLazy getInstance() {
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazy();
        }
        return singletonLazy;
    }
}

  • 優(yōu)點:對getInstance()加了鎖的處理,保證了同一時刻只能有一個線程訪問并獲得實例.
  • 缺點:也很明顯,因為synchronized是修飾整個方法,每個線程訪問都要進行同步,而其實這個方法只執(zhí)行一次實例化代碼就夠了,每次都同步方法顯然效率低下,為了改進這種寫法,就有了下面的雙重檢查懶漢式。

3.懶漢式(雙重校驗鎖)

package com.d4c.example;

/**
 * 懶漢式(DBL)
 * volatile關鍵字修飾,防止指令重排
 * Double Check Lock(DCL) 雙重鎖校驗
 */
public class SingletonLazyDBL {

    private static volatile SingletonLazyDBL singletonLazy = null;

    private SingletonLazyDBL() {
    }

    public static SingletonLazyDBL getInstance() {
        if (singletonLazy == null) {
            synchronized (SingletonLazyDBL.class) {
                if (singletonLazy == null) {
                    singletonLazy = new SingletonLazyDBL();
                }
            }
        }
        return singletonLazy;
    }
}

  • 優(yōu)點:用了兩個if判斷,也就是Double-Check,并且同步的不是方法,而是代碼塊,效率較高。

為什么要做兩次判斷呢?這是為了線程安全考慮,還是那個場景,對象還沒實例化,兩個線程A和B同時訪問靜態(tài)方法并同時運行到第一個if判斷語句,這時線程A先進入同步代碼塊中實例化對象,結束之后線程B也進入同步代碼塊,如果沒有第二個if判斷語句,那么線程B也同樣會執(zhí)行實例化對象的操作了。

4.靜態(tài)內(nèi)部類方式

package com.d4c.example;

/**
 * 懶漢式(內(nèi)部類方式)
 */
public class SingletonInnerType {

    private SingletonInnerType() {
    }

    private static class SingletonHolder {
        public static volatile SingletonInnerType SINGLETON_INNER_TYPE = new SingletonInnerType();
    }

    public static SingletonInnerType getInstance() {
        return SingletonHolder.SINGLETON_INNER_TYPE;
    }
}

似乎靜態(tài)內(nèi)部類看起來已經(jīng)是最完美的方法了,其實不是,可能還存在反射攻擊或者反序列化攻擊

 public static void main(String[] args) throws Exception {
        SingletonInnerType singleton = SingletonInnerType.getInstance();
        Constructor<SingletonInnerType> constructor = SingletonInnerType.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        SingletonInnerType newSingleton = constructor.newInstance();
        System.out.println(singleton == newSingleton);
    }

或者引入反序列化后,也不是單例的了。

反序列化須引入依賴(方便操作)

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>
  public static void main(String[] args) throws Exception {
        SingletonInnerType instance = SingletonInnerType.getInstance();
        System.out.println("instance = " + instance);
        byte[] serialize = SerializationUtils.serialize(instance);
        SingletonInnerType newInstance = SerializationUtils.deserialize(serialize);
        System.out.println("newInstance = " + newInstance);
        System.out.println(instance == newInstance);
    }

所以,反射攻擊或者反序列化都導致單例失敗。

解決方法,禁止反射就可以了。
優(yōu)化后

package com.d4c.example;

import java.lang.reflect.Constructor;
/**
 * 懶漢式(內(nèi)部類方式)
 */
public class SingletonInnerType {

    private SingletonInnerType() {
    //反射的情況能防住,序列化,反序列化的方式防不住
         if (SingletonHolder.SINGLETON_INNER_TYPE!=null){
            throw new RuntimeException("破解錯誤!");
        }
    }

    private static class SingletonHolder {
        public static volatile SingletonInnerType SINGLETON_INNER_TYPE = new SingletonInnerType();
    }

    public static SingletonInnerType getInstance() {
        return SingletonHolder.SINGLETON_INNER_TYPE;
    }

    public static void main(String[] args) throws Exception {
        //這種破解能防住
        SingletonInnerType singleton = SingletonInnerType.getInstance();
        Constructor<SingletonInnerType> constructor = SingletonInnerType.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        SingletonInnerType newSingleton = constructor.newInstance();
        System.out.println(singleton == newSingleton);

         //下面這種防不住
        SingletonInnerType instance = SingletonInnerType.getInstance();
        System.out.println("instance = " + instance);
        byte[] serialize = SerializationUtils.serialize(instance);
        SingletonInnerType newInstance = SerializationUtils.deserialize(serialize);
        System.out.println("newInstance = " + newInstance);
        System.out.println(instance == newInstance);
    }
}

  • 優(yōu)點:線程安全,調用效率高,可以延時加載

這是很多開發(fā)者推薦的一種寫法,這種靜態(tài)內(nèi)部類方式在SingletonInnerType 類被裝載時并不會立即實例化,而是在需要實例化時,調用getInstance方法,才會裝載SingletonHolder 類,從而完成對象的實例化。同時,因為類的靜態(tài)屬性只會在第一次加載類的時候初始化,也就保證了SingletonHolder 中的對象只會被實例化一次,并且這個過程也是線程安全的。

靜態(tài)內(nèi)部類也有著一個致命的缺點,就是傳參的問題,由于是靜態(tài)內(nèi)部類的形式去創(chuàng)建單例的,故外部無法傳遞參數(shù)進去,例如Context這種參數(shù),所以,我們創(chuàng)建單例時,可以在靜態(tài)內(nèi)部類與DCL模式里自己斟酌。

5.枚舉方式


package com.d4c.example;

public enum Singleton {

    INSTANCE;

    public void doSomething() {
        System.out.println("doSomething");
    }

}


//調用

public class Main {

    public static void main(String[] args) {
        Singleton.INSTANCE.doSomething();
    }

}

這種寫法在《Effective JAVA》中大為推崇,它可以解決兩個問題:

  • 1)線程安全問題。因為Java虛擬機在加載枚舉類的時候會使用ClassLoader的方法,這個方法使用了同步代碼塊來保證線程安全。

  • 2)避免反序列化破壞對象,因為枚舉的反序列化并不通過反射實現(xiàn)。
    好了,單例模式的幾種寫法就介紹到這了,最后簡單總結一下單例模式的優(yōu)缺點

  • 缺點:不能延時加載。

總結:

匿名內(nèi)部類的方式和單元素的枚舉類型能夠防住反射或反序列化的攻擊,其他幾種則不行。所以推薦這兩種方式創(chuàng)建單例。

引用文章
設計模式:單例模式 (關于餓漢式和懶漢式)
【一起學系列】之單例模式:只推薦三種~
Java 利用枚舉實現(xiàn)單例模式
Java單例模式:為什么我強烈推薦你用枚舉來實現(xiàn)單例模式
Java單例---反射攻擊破壞單例和解決方法

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容