Java設計模式之單例模式理解

前言

本片博客主要記錄Java23種設計模式中的創建型模式中的單例模式。單例模式可分為兩類,一種是餓漢式,一種是懶漢式。餓漢式的三種設計方式(靜態變量方式、靜態代碼塊方式、枚舉方式),懶漢式(單鎖檢查方式、雙鎖檢查方式、靜態內部類方式),以及破壞單例模式的兩種方式:序列化反序列化,反射。
設計模式,是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性、程序的重用性

image

單例模式結構

私有的構造方法【核心】
私有的、靜態的實例化變量應用
提供一個公有的、靜態的獲取類實例對象方法

單例模式

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。它提供了一種創建對象的最佳方式。

單例模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

1、單例類只能有一個實例。
2、單例類必須自己創建自己的唯一實例。
3、單例類必須給所有其他對象提供這一實例。

餓漢式

靜態變量方式
  • 直接在創建對象時賦值
package hello;


public class Hello {


    public static void main(String[] args) {
		//只能通過getSingleton方法獲取,不能通過new方法創建
        Singleton singleton = Singleton.getSingleton();
        Singleton singleton11 = Singleton.getSingleton();

        //通過hashCode查看是否是同一個對象
        System.out.println(singleton.hashCode());
        System.out.println(singleton11.hashCode());

    }
}

class Singleton{

    //私有構造方法,這樣外界就不能創建了
    private Singleton(){
    }

    //自己創建一個對象
    private static Singleton singleton = new Singleton();

    //給外界提供一個方法用於訪問
    public static Singleton getSingleton(){
        return singleton;
    }

}



靜態代碼塊方式
  • 在靜態代碼塊里賦值
package hello;

public class Hello {

    public static void main(String[] args) {
		//只能通過getSingleton方法獲取,不能通過new方法創建
        Singleton singleton = Singleton.getSingleton();
        Singleton singleton11 = Singleton.getSingleton();

        //通過hashCode查看是否是同一個對象
        System.out.println(singleton.hashCode());
        System.out.println(singleton11.hashCode());

    }
}

class Singleton{

    //私有構造方法,這樣外界就不能創建了
    private Singleton(){
    }

    //自己創建一個對象,但是不實例
    private static Singleton singleton;

    //通過靜態代碼塊賦值
    static {
        singleton = new Singleton();
    }

    //給外界提供一個方法用於訪問
    public static Singleton getSingleton(){
        return singleton;
    }

}
枚舉方式

由於上面檢測代碼相同,就不在這裡重複複製了。
只需要把class Singleton改為下面就行了

enum Singleton{
    SINGLETON;
}

懶漢式

單鎖檢查模式
package hello;

public class Hello {


    public static void main(String[] args) {

        Singleton singleton = Singleton.getSingleton();
        Singleton singleton11 = Singleton.getSingleton();

        //通過hashCode查看是否是同一個對象
        System.out.println(singleton.hashCode());
        System.out.println(singleton11.hashCode());

    }
}

class Singleton{

    //私有構造方法,這樣外界就不能創建了
    private Singleton(){ }

    //自己聲明一個對象
    private static Singleton singleton;

    //給外界提供一個方法用於訪問
    public static synchronized Singleton getSingleton(){
        //判讀singleton是否為null,如果是null就創建,否者直接返回
        if (singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }

}
雙重檢查鎖模式

上面的驗證都是一樣的,這裡只顯示Singleton類就行

class Singleton{

    //私有構造方法,這樣外界就不能創建了
    private Singleton(){ }

    //自己聲明一個對象
    private static volatile Singleton singleton;

    //給外界提供一個方法用於訪問
    public static synchronized Singleton getSingleton(){
        //判讀singleton是否為null,如果是null就創建,否者直接返回
        if (singleton == null){
            synchronized (Singleton.class){
                if (singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

靜態內部類實現

  • 靜態內部類單例模式是一種優秀的單例模式,是比較常用的單例模式。在沒有加任何鎖時保證線程安全,並且沒有任何性能影響和空間浪費。

  • 在加載Singleton時不會初始化singleton,只有第一次調用getSingleton()時。JVM加載SingletonHolder初始化singleton。

class Singleton{

    //私有構造方法,這樣外界就不能創建了
    private Singleton(){ }

    //定義一個靜態內部類
    private static class SingletonHolder{
        //只會初始化一次
        private static final Singleton singleton= new Singleton();
    }
    //給外界提供一個方法用於訪問
    public static synchronized Singleton getSingleton(){
        return SingletonHolder.singleton;
    }

}

破壞單例模式

  • 破壞單例模式的方式有兩種一種是序列化反序列化,另一種是反射,這裡我們指記錄反射
  • 道高一尺,魔高一丈。有模式就會有破壞,有破壞還會有防破壞,但是還有反反破壞。這裏面就多了。

通過反射破壞單例模式

package hello;

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

public class Hello {


    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        //獲取Singleton位元組碼對象
        Class<Singleton> singletonClass = Singleton.class;
        //獲取無參構造方法
        Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
        //取消訪問檢查
        declaredConstructor.setAccessible(true);
        //創建Singleton對象
        Singleton singleton = (Singleton) declaredConstructor.newInstance();
        Singleton singleton1 = (Singleton) declaredConstructor.newInstance();
        //通過hashCode查看是否是同一個對象
        System.out.println(singleton.hashCode());
        System.out.println(singleton1.hashCode());
    }
}

class Singleton{

    //私有構造方法,這樣外界就不能創建了
    private Singleton(){ }

    //定義一個靜態內部類
    private static class SingletonHolder{
        //只會初始化一次
        private static final Singleton singleton= new Singleton();
    }
    //給外界提供一個方法用於訪問
    public static synchronized Singleton getSingleton(){
        return SingletonHolder.singleton;
    }

}

單例模式優缺點

優點:

  • 單例模式在內存中只有一個實例,減少內存開支,特別是一個對象需要頻繁地創建銷毀時,而且創建或銷毀時性能又無法優化,單例模式就非常明顯了
  • 單例模式只生成一個實例,減少系統的性能開銷
  • 單例模式可以避免對資源的多重佔用
  • 單例模式可以在系統設置全局的訪問點,優化和共享資源訪問
    缺點:
  • 不適用於變化的對象
  • 由於單例模式沒有抽象層,所以擴展困難
  • 單例類的職責過重,在一定程度上違背了「單一職責原則」
  • 單一職責原則:一個類,應該只有一個職責