設計模式六大原則之單例模式


  前言:不斷學習就是程序員的宿命

一、單例模式

  所謂的單例設計模式,就是采取一定的方法保證整個的軟件系統中,對某個類只能存在一個對象實例,並且該類只能提供一個取得對象實例的方法(靜態方法)。比如Hibernate的SessionFactory,它充當數據存儲源的代理,並負責創建session對象。SessionFactory並不是輕量級的

  單例模式有以下8種方式:

  

二、餓漢式(靜態常量)

public class Singleton01 {
    //本類內部創建對象實例
    private final static Singleton01 instance=new Singleton01();
    //構造器私有化,外部不能new
   private Singleton01(){}
   //提供一個公有靜態方法,返回實例對象
    private static Singleton01 getInstance(){
       return instance;
    }

    public static void main(String[] args) {
        //測試
        Singleton01 instance1 = Singleton01.getInstance();
        Singleton01 instance2 = Singleton01.getInstance();
        System.out.println(instance1==instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance1.hashCode());
    }
}
餓漢式(靜態常量)

分析:

  (1)優點:這種寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。

  (2)缺點:在類裝載的時候就完成實例化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費

三、餓漢式(靜態代碼塊)

public class Singleton02 {
    //本類內部創建對象實例
    private  static Singleton02 instance;
    //構造器私有化, 外部不能new
    private Singleton02(){}
    // 在靜態代碼塊中,創建單例對象
    static {
        instance = new Singleton02();
    }
    //提供一個公有的靜態方法,返回實例對象
    public static Singleton02 getInstance() {
        return instance;
    }
    public static void main(String[] args) {
        //測試
        Singleton02 instance = Singleton02.getInstance();
        Singleton02 instance2 = Singleton02.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}
餓漢式(靜態代碼塊)

分析:

  (1)這種方式和上述方式類似,只不過將類實例化的過程放在了靜態代碼塊中,也就是在裝載的時候執行靜態代碼中的代碼,初始化類的實例

  (2)結論:單例方式可用,但可能會造成內存浪費

四、懶漢式(線程不安全)

/**
 * @ClassName: Singleton03
 * @Description: 懶漢式-線程不安全
 * @Author: xiedong
 * @Date: 2020/4/5 0:19
 */
public class Singleton03 {
    private static Singleton03 instance;

    private Singleton03() {}

    //提供一個靜態的公有方法,當使用到該方法時,才去創建 instance
    public static Singleton03 getInstance() {
        if(instance == null) {
            instance = new Singleton03();
        }
        return instance;
    }

    public static void main(String[] args) {
        System.out.println("懶漢式1 , 線程不安全~");
        Singleton03 instance = Singleton03.getInstance();
        Singleton03 instance2 = Singleton03.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}
懶漢式(線程不安全)

分析:

  (1)起到了Lazy Loading的效果,但是只能在單線程下使用

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

  (3)結論:實際開發中,不要使用這種方式

五、懶漢式(同步方法保證線程安全)

/**
 * @ClassName: Singleton04
 * @Description: 懶漢式-同步方法
 * @Author: xiedong
 * @Date: 2020/4/5 0:53
 */
public class Singleton04 {
    private static Singleton04 instance;

    private Singleton04() {}

    //提供一個靜態的公有方法,加入同步處理的代碼,解決線程安全問題
    //即懶漢式
    public static synchronized Singleton04 getInstance() {
        if(instance == null) {
            instance = new Singleton04();
        }
        return instance;
    }

    public static void main(String[] args) {
        System.out.println("懶漢式2 , 線程安全~");
        Singleton04 instance = Singleton04.getInstance();
        Singleton04 instance2 = Singleton04.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}
懶漢式(同步方法)

分析:

  (1)解決線程安全問題

  (2)效率太低了,每個線程在想獲得類的實例的時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次實例化代碼就夠了,后面的想要獲得該類實例,直接return就行了,方法同步效率太低了。

  (3)結論:在實際開發中,不推薦使用這種方式

六、懶漢式(同步代碼塊保證線程安全)

/**
 * @ClassName: Singleton05
 * @Description: 懶漢式-同步代碼塊
 * @Author: xiedong
 * @Date: 2020/4/5 0:56
 */
public class Singleton05 {
    private static Singleton05 instance;

    private Singleton05() {
    }
    
    public static Singleton05 getInstance() {
        if (instance == null) {
            synchronized (Singleton05.class) {
                instance = new Singleton05();
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton05 instance = Singleton05.getInstance();
        Singleton05 instance2 = Singleton05.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}
同步代碼塊

分析:

  (1)這種方式,本意是想對上述(同步方法)實現方式的改進,因為前面同步方法效率太低了,改為同步產生實例化的代碼塊

  (2)但是這種同步並不能起到線程同步的作用。假如一個線程進入了if(instance==null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會產生多個實例。

  (3)結論:實際開發中,不能使用這種方式

七、雙重檢查

/**
 * @ClassName: Singleton06
 * @Description: 雙重檢查
 * @Author: xiedong
 * @Date: 2020/4/5 1:07
 */
public class Singleton06 {
    private static volatile Singleton06 instance;

    private Singleton06() {}

    //提供一個靜態的公有方法,加入雙重檢查代碼,解決線程安全問題, 同時解決懶加載問題
    //同時保證了效率, 推薦使用

    public static synchronized Singleton06 getInstance() {
        if(instance == null) {
            synchronized (Singleton06.class) {
                if(instance == null) {
                    instance = new Singleton06();
                }
            }

        }
        return instance;
    }
    public static void main(String[] args) {
        System.out.println("雙重檢查");
        Singleton06 instance = Singleton06.getInstance();
        Singleton06 instance2 = Singleton06.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());

    }
}
雙重檢查

分析:

  (1)Double-Check概念是多線程開發中常使用到的,如代碼中所示,兩次if(singleton==null)檢查,這樣就保證線程安全了。

  (2)這樣實例化代碼只用執行一次,后面再次訪問到時,判斷if(singleton==null)直接return實例對象,也避免了反復進行方法同步。

  (3)線程安全;延遲加載;效率較高

  (4)結論:在實際開發中,推薦使用這種單例設計模式

八、靜態內部類

/**
 * @ClassName: Singleton7
 * @Description:
 * @Author: xiedong
 * @Date: 2020/4/5 1:20
 */
public class Singleton7 {
    private static volatile Singleton7 instance;

    //構造器私有化
    private Singleton7() {}

    //寫一個靜態內部類,該類中有一個靜態屬性 Singleton
    private static class SingletonInstance {
        private static final Singleton7 INSTANCE = new Singleton7();
    }

    //提供一個靜態的公有方法,直接返回SingletonInstance.INSTANCE

    public static synchronized Singleton7 getInstance() {

        return SingletonInstance.INSTANCE;
    }
    public static void main(String[] args) {
        System.out.println("使用靜態內部類完成單例模式");
        Singleton7 instance = Singleton7.getInstance();
        Singleton7 instance2 = Singleton7.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());

    }
}
靜態內部類實現單例

分析:

  (1)這種方式采用了類裝載的機制來保證初始化實例時只有一個線程

  (2)靜態內部類方式在Singleton07類(外部類)被裝載時並不會立即實例化,而是在需要實例化時,調用getInstance()方法,才會裝載SingletonInstance類,從而完成Singleton的實例化

  (3)類的靜態屬性只會在第一次加載類的時候初始化,所以在這里,JVM幫助我們保證了線程的安全性,在類進行初始化時,別的線程是無法進入的。

  (4)優點:避免了線程不安全,利用靜態內部類特點實現延遲加載,效率高

  (5)結論:推薦使用

九、枚舉

/**
 * @ClassName: Singleton08
 * @Description:
 * @Author: xiedong
 * @Date: 2020/4/5 1:30
 */
public class Singleton08 {
    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance == instance2);

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());

        instance.sayOK();
    }
}

//使用枚舉,可以實現單例, 推薦
enum Singleton {
    INSTANCE; //屬性

    public void sayOK() {
        System.out.println("ok~");
    }
}
枚舉實現單例

分析:

  (1)這里借助JDK1.5中添加的枚舉來實現單例模式。不僅能夠避免多線程同步問題,而且還能防止反序列化重新創建新的對象

  (2)這種方式是Effective Java作者Josh Bloch提倡的方式

  (3)結論:推薦使用

十、單例模式在JDK應用舉例

 

  可以發現,JDK中使用餓漢式-靜態屬性

十一、總結

(1)單例模式保證了系統內存中該類只存在一個對象,節省了系統資源,對於一些需要頻繁創建銷毀的對象,使用單例模式可以提高系統性能

(2)當想實例化一個單例類的時候,必須要記住使用相應獲取對象的方法,而不是new

(3)單例模式的使用場景:需要頻繁的創建和銷毀對象、創建對象時過多或耗費資源過多(即:重量級對象),但又經常用到的對象、工具類對象、頻繁訪問數據庫或文件的對象(比如數據源、session工廠等)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM