天天看點

【Java -- 設計模式】單例模式(Singleton)

1. 定義 & 特點

  • 定義

    指一個類隻有一個執行個體,且該類能自行建立這個執行個體的一種模式。例如,Windows 中隻能打開一個任務管理器,這樣可以避免因打開多個任務管理器視窗而造成記憶體資源的浪費,或出現各個視窗顯示内容的不一緻等錯誤。

  • 特點:
  • 單例類隻有一個執行個體對象;
  • 該單例對象必須由單例類自行建立;
  • 單例類對外提供一個通路該單例的全局通路點。

2. 優點 & 缺點

  • 優點:
  • 單例模式可以保證記憶體裡隻有一個執行個體,減少了記憶體的開銷。
  • 可以避免對資源的多重占用。
  • 單例模式設定全局通路點,可以優化和共享資源的通路。
  • 缺點:
  • 單例模式一般沒有接口,擴充困難。如果要擴充,則除了修改原來的代碼,沒有第二種途徑,違背開閉原則。
  • 在并發測試中,單例模式不利于代碼調試。在調試過程中,如果單例中的代碼沒有執行完,也不能模拟生成一個新的對象。
  • 單例模式的功能代碼通常寫在一個類中,如果功能設計不合理,則很容易違背單一職責原則。

3. 應用場景

對于 ​

​Java​

​​ 來說,單例模式可以保證在一個 ​

​JVM​

​ 中隻存在單一執行個體。單例模式的應用場景主要有以下幾個方面。

  • 需要頻繁建立的一些類,使用單例可以降低系統的記憶體壓力,減少 GC。
  • 某類隻要求生成一個對象的時候,如一個班中的班長、每個人的身份證号等。
  • 某些類建立執行個體時占用資源較多,或執行個體化耗時較長,且經常使用。
  • 某類需要頻繁執行個體化,而建立的對象又頻繁被銷毀的時候,如多線程的線程池、網絡連接配接池等。
  • 頻繁通路資料庫或檔案的對象。
  • 對于一些控制硬體級别的操作,或者從系統上來講應當是單一控制邏輯的操作,如果有多個執行個體,則系統會完全亂套。
  • 當對象需要被共享的場合。由于單例模式隻允許建立一個對象,共享該對象可以節省記憶體,并加快對象通路速度。如 Web 中的配置對象、資料庫的連接配接池等。

4. 結構 & 實作

單例模式是設計模式中最簡單的模式之一。通常,普通類的構造函數是公有的,外部類可以通過“new 構造函數()”來生成多個執行個體。但是,如果将類的構造函數設為私有的,外部類就無法調用該構造函數,也就無法生成多個執行個體。這時該類自身必須定義一個靜态私有執行個體,并向外提供一個靜态的公有函數用于建立或擷取該靜态私有執行個體。

4.1 結構

單例模式的主要角色如下。

  • 單例類:包含一個執行個體且能自行建立這個執行個體的類。
  • 通路類:使用單例的類。

結構圖

【Java -- 設計模式】單例模式(Singleton)

4.2 實作

  • 第 1 種:懶漢式單例

    該模式的特點是類加載時沒有生成單例,隻有當第一次調用​​

    ​getlnstance​

    ​ 方法時才去建立這個單例。代碼如下:
public class LazySingleton {
    private static volatile LazySingleton instance = null;    //保證 instance 在所有線程中同步

    private LazySingleton() {
    }    //private 避免類在外部被執行個體化

    public static synchronized LazySingleton getInstance() {
        //getInstance 方法前加同步
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}      

注意:如果編寫的是多線程程式,則不要删除上例代碼中的關鍵字 ​

​volatile​

​​ 和 ​

​synchronized​

​,否則将存線上程非安全的問題。如果不删除這兩個關鍵字就能保證線程安全,但是每次通路時都要同步,會影響性能,且消耗更多的資源,這是懶漢式單例的缺點。

  • 第 2 種:餓漢式單例

    該模式的特點是類一旦加載就建立一個單例,保證在調用​​

    ​getInstance​

    ​ 方法之前單例已經存在了。
public class HungrySingleton {
    private static final HungrySingleton instance = new HungrySingleton();
    private HungrySingleton() {
    }
    public static HungrySingleton getInstance() {
        return instance;
    }
}      

餓漢式單例在類建立的同時就已經建立好一個靜态的對象供系統使用,以後不再改變,是以是線程安全的,可以直接用于多線程而不會出現問題。

5. 執行個體

【例1】用懶漢式單例模式模拟産生美國當今總統對象。

分析:在每一屆任期内,美國的總統隻有一人,是以本執行個體适合用單例模式實作,下圖所示是用懶漢式單例實作的結構圖。

【Java -- 設計模式】單例模式(Singleton)

執行個體代碼:

public class SingletonLazy {
    public static void main(String[] args) {
        President zt1 = President.getInstance();
        zt1.getName();    //輸出總統的名字
        President zt2 = President.getInstance();
        zt2.getName();    //輸出總統的名字
        if (zt1 == zt2) {
            System.out.println("他們是同一人!");
        } else {
            System.out.println("他們不是同一人!");
        }
    }
}
class President {
    private static volatile President instance = null;    //保證instance在所有線程中同步
    //private避免類在外部被執行個體化
    private President() {
        System.out.println("産生一個總統!");
    }
    public static synchronized President getInstance() {
        //在getInstance方法上加同步
        if (instance == null) {
            instance = new President();
        } else {
            System.out.println("已經有一個總統,不能産生新總統!");
        }
        return instance;
    }
    public void getName() {
        System.out.println("我是美國總統:特朗普。");
    }
}      

輸出結果:

産生一個總統!
我是美國總統:特朗普。
已經有一個總統,不能産生新總統!
我是美國總統:特朗普。
他們是同一人!      
import java.awt.*;
import javax.swing.*;
public class SingletonEager {
    public static void main(String[] args) {
        JFrame jf = new JFrame("餓漢單例模式測試");
        jf.setLayout(new GridLayout(1, 2));
        Container contentPane = jf.getContentPane();
        Bajie obj1 = Bajie.getInstance();
        contentPane.add(obj1);
        Bajie obj2 = Bajie.getInstance();
        contentPane.add(obj2);
        if (obj1 == obj2) {
            System.out.println("他們是同一人!");
        } else {
            System.out.println("他們不是同一人!");
        }
        jf.pack();
        jf.setVisible(true);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}
class Bajie extends JPanel {
    private static Bajie instance = new Bajie();
    private Bajie() {
        JLabel l1 = new JLabel(new ImageIcon("src/Bajie.jpg"));
        this.add(l1);
    }
    public static Bajie getInstance() {
        return instance;
    }
}