設計模式(二)——設計模式類型&單例模式

一、設計模式類型

設計模式分爲三種類型(共23種):
      1)創建型模式:單例模式、抽象工廠模式、原型模式、建造者模式、工廠模式;
      2)結構型模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式;
      3)行爲型模式:模板方法模式、命令模式、訪問者模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態模式、策略模式、職責鏈模式(責任鏈模式)。

二、單例模式

單例模式介紹

      所謂類的單例設計模式,就是採取一定的方法保證子在整個的軟件系統中,對某個類只能存在一個對象實例,並且該類只提供一個取得其對象實例的方法(靜態方法)。
      單例模式有八種方式:

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

    public static void main(String[] args) {
        //測試
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);//true
        System.out.println("instance.hashCode()="+instance.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }

}

//餓漢式(靜態變量)
class Singleton{

    //1、本類內部創建對象實例
    private final static Singleton instance = new Singleton();

    //2、構造器私有化
    private Singleton(){

    }

    //3、提供一個公有的靜態方法,返回實例對象
    public static Singleton getInstance(){
        return instance;
    }

}

在這裏插入圖片描述
      餓漢式(靜態常量)優缺點說明:

  • 優點:這種寫法比較簡單,就是在類加載的時候就完成實例化。避免了線程同步的問題。
  • 缺點:在類裝載的時候就完成實例化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費。
  • 這種方式基於classloader機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,在單例模式中大多數都是調用getInstance方法,但是導致類裝載的原因有很多種,因此不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance就沒有達到 lazy loading的效果。
  • 結論:這種單例模式可用,可能造成內存浪費。
餓漢式(靜態代碼塊)
public class Singleton2 {

    public static void main(String[] args) {
        //測試
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);//true
        System.out.println("instance.hashCode()="+instance.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }

}

//餓漢式(靜態代碼塊)
class Singleton{

    //1、創建變量
    private static Singleton instance;

    //2、構造器私有化
    private Singleton(){

    }
    //3、靜態代碼塊
    static{
        instance = new Singleton();
    }

    //3、提供一個公有的靜態方法,返回實例對象
    public static Singleton getInstance(){
        return instance;
    }

}

      餓漢式(靜態代碼塊)優缺點說明:
1)這種方式和上面的方式其實類似,只不過將類實例化的過程放在了靜態代碼塊中,也是在類裝載的時候,就執行靜態代碼塊中的代碼,初始化類的實例。優缺點和上面一樣的。
2)結論:這種單例模式可用,但是可能造成內存浪費。

懶漢式(線程不安全)
public class Singleton3 {

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

    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){}

    //提供一個靜態的共有方法,當使用到該方法時,纔去創建instance
    //即懶漢式
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

}

4
      優缺點說明:
1)起到了lazy loading的效果,但是隻能在單線程下使用;
2)如果在多線程下,一個線程進入了if(instance == null)判斷語句,還未來得及往下執行,另一個判斷也通過了這個判斷語句,這時便會產生多個實例。所以在多線程環境下不可使用這種方式。
3)結論:在實際開發中,不要使用這種方式。

懶漢式(線程安全)
public class Singleton4 {

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

    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){}

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

}

在這裏插入圖片描述
      優缺點說明:
1)解決了線程不安全問題;
2)效率太低了,每個線程在想獲得類的實例的時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次實例化代碼就夠了,後面的想獲得該實例,直接return就行了。方法進行同步效率太低。
3)結論:在實際開發中,不推薦這種方式。

懶漢式(線程安全,雙重檢查)
public class Singleton6 {

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

    }
}

class Singleton{
    //volatile修飾後,一旦instance修改會立即更新到主存
    private static volatile Singleton instance;

    private Singleton(){}

    //提供一個靜態的公有方法,加入雙重檢查代碼,解決線程安全問題,同時解決懶加載問題
    //同時保證了效率,推薦使用
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

在這裏插入圖片描述
      優缺點說明:
1)Double-Check概念是多線程開發中常使用到的,如代碼中所示,我們進行了兩次if(instance == null)檢查,這樣就可以保證線程安全了;
2)這樣,實例化代碼只用執行一次,後面再次訪問時,判斷if(instance == null),直接return實例化對象,也避免的反覆進行方法同步;
3)線程安全;延遲加載;效率較高;
4)結論:在實際開發中,推薦使用這種單例設計模式。

懶漢式(使用靜態內部類完成單例模式)
public class Singleton7 {

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

    }
}

//靜態內部類完成,推薦使用
//好處:1、在Singleton進行類加載的時候,SingletonInstance並不會馬上執行,
//      即靜態內部類在外部類加載的時候不會被加載,從而保證了懶加載
//      2、在調用getInstance()方法時,會去取(即加載)靜態內部類裏面的屬性。jvm在裝載類的時候是安全的,
//        這裏使用了JVM底層提供的類的裝載機制,保證了初始化是線程安全的
class Singleton{
    //volatile修飾後,一旦instance修改會立即更新到主存
    private static volatile Singleton instance;

    private Singleton(){}

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

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

        return SingletonInstance.INSTANCE;
    }

}

在這裏插入圖片描述
      優缺點說明:
1)這種方式採用了類裝載的機制來保證初始化實例時只有一個線程;
2)靜態內部類方式在Singleton類被裝載時並不會立即實例化,而是在需要實例化時,調用getInstance方法,纔會裝載SingletonInstance類,從而完成Singleton的實例化;
3)類的靜態屬性只會在第一次加載類的時候初始化,所以在這裏,JVM幫助我們保證了線程的安全性,在類進行初始化時,別的線程是無法進入的;
4)優點:避免了線程不安全,利用靜態內部類特點實現延遲加載,效率高;
5)結論:推薦使用

懶漢式(枚舉)
public class Singleton8 {

    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)結論:推薦使用。

三、單例模式注意事項和細節說明

  • 單例模式保證了系統內存中該類只存在一個對象,節省了系統資源,對於一些需要頻繁創建的對象,使用單例模式可以提高系統性能;
  • 當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法,而不是使用new;
  • 單例模式使用的場景:需要頻繁的進行創建和銷燬的對象、創建對象時耗時過多或耗費資源過多(即:重量級對象),但又經常用到的對象、工具類對象、頻繁訪問數據庫或文件的對象(比如數據源、session工廠等)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章