單例模式的定義與特點
單例(Singleton)模式的定義:指一個類只有一個實例,且該類能自行創建這個實例的一種模式。例如,Windows 中只能打開一個任務管理器,這樣可以避免因打開多個任務管理器窗口而造成內存資源的浪費,或出現各個窗口顯示內容的不一致等錯誤。
在計算機系統中,還有 Windows 的回收站、操作系統中的文件系統、多線程中的線程池、顯卡的驅動程序對象、打印機的後臺處理服務、應用程序的日誌對象、數據庫的連接池、網站的計數器、Web 應用的配置對象、應用程序中的對話框、系統中的緩存等常常被設計成單例。
單例模式有 3 個特點:
- 單例類只有一個實例對象;
- 該單例對象必須由單例類自行創建;
- 單例類對外提供一個訪問該單例的全局訪問點;
單例模式的結構與實現
單例模式是設計模式中最簡單的模式之一。通常,普通類的構造函數是公有的,外部類可以通過“new 構造函數()”來生成多個實例。但是,如果將類的構造函數設爲私有的,外部類就無法調用該構造函數,也就無法生成多個實例。這時該類自身必須定義一個靜態私有實例,並向外提供一個靜態的公有函數用於創建或獲取該靜態私有實例。
下面來分析其基本結構和實現方法。
1. 單例模式的結構
單例模式的主要角色如下。
- 單例類:包含一個實例且能自行創建這個實例的類。
- 訪問類:使用單例的類。
其結構如圖 1 所示。
圖1 單例模式的結構圖
2. 單例模式的實現
Singleton 模式通常有兩種實現形式。
第 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;
}
}
餓漢式單例在類創建的同時就已經創建好一個靜態的對象供系統使用,以後不再改變,所以是線程安全的,可以直接用於多線程而不會出現問題。
單例模式的應用實例
【例1】用懶漢式單例模式模擬產生美國當今總統對象。
分析:在每一屆任期內,美國的總統只有一人,所以本實例適合用單例模式實現,圖 2 所示是用懶漢式單例實現的結構圖。
圖2 美國總統生成器的結構圖
程序代碼如下:
import java.util.concurrent.CountDownLatch;
/**
* @ClassName Singleton
* @Description TODO 單例(Singleton)模式的定義:指一個類只有一個實例,且該類能自行創建這個實例的一種模式。例如,Windows 中只能打開一個任務管理器,這樣可以避免因打開多個任務管理器窗口而造成內存資源的浪費,或出現各個窗口顯示內容的不一致等錯誤。
* @Author Evan
* @Date 2019/12/11 18:33
* @Version 1.0
**/
public class Singleton {
public static CountDownLatch latch = new CountDownLatch(11);
//Volatile限定的是從緩存讀取時刻的校驗.
private static volatile Singleton instance = null; //保證 instance 在所有線程中同步
//private避免類在外部被實例化
private Singleton() {
System.out.println("產生一個總統!");
}
//在getInstance方法上加同步
public static synchronized Singleton getInstance() {
try {
//latch.await();
if (instance == null) {
instance = new Singleton();
} else {
System.out.println("已經有一個總統,不能產生新總統!");
}
return instance;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void getName() {
System.out.println("我是美國總統:特朗普。");
}
public static void main(String[] args) {
//測試一
Singleton zt1= Singleton.getInstance();
zt1.getName(); //輸出總統的名字
Singleton zt2=Singleton.getInstance();
zt2.getName(); //輸出總統的名字
if(zt1==zt2)
{
System.out.println("他們是同一人!");
}
else
{
System.out.println("他們不是同一人!");
}
//測試二 單利線程安全
for (int i = 0; i < 10; i++) {
Thread th = new Thread(Singleton::getInstance);
th.start();
latch.countDown();
}
}
}
程序運行結果如下:
產生一個總統!
我是美國總統:特朗普。
已經有一個總統,不能產生新總統!
我是美國總統:特朗普。
他們是同一人!
已經有一個總統,不能產生新總統!
已經有一個總統,不能產生新總統!
已經有一個總統,不能產生新總統!
已經有一個總統,不能產生新總統!
已經有一個總統,不能產生新總統!
已經有一個總統,不能產生新總統!
已經有一個總統,不能產生新總統!
已經有一個總統,不能產生新總統!
已經有一個總統,不能產生新總統!
已經有一個總統,不能產生新總統!
【例2】用餓漢式單例模式模擬產生豬八戒對象。
分析:同上例類似,豬八戒也只有一個,所以本實例同樣適合用單例模式實現。本實例由於要顯示豬八戒的圖像(點此下載該程序所要顯示的豬八戒圖片),所以用到了框架窗體 JFrame 組件,這裏的豬八戒類是單例類,可以將其定義成面板 JPanel 的子類,裏面包含了標籤,用於保存豬八戒的圖像,客戶窗體可以獲得豬八戒對象,並顯示它。圖 3 所示是用餓漢式單例實現的結構圖。
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;
}
}
程序運行結果如圖 4 所示。
單例模式的應用場景
前面分析了單例模式的結構與特點,以下是它通常適用的場景的特點。
- 在應用場景中,某類只要求生成一個對象的時候,如一個班中的班長、每個人的身份證號等。
- 當對象需要被共享的場合。由於單例模式只允許創建一個對象,共享該對象可以節省內存,並加快對象訪問速度。如 Web 中的配置對象、數據庫的連接池等。
- 當某類需要頻繁實例化,而創建的對象又頻繁被銷燬的時候,如多線程的線程池、網絡連接池等。
單例模式的擴展
單例模式可擴展爲有限的多例(Multitcm)模式,這種模式可生成有限個實例並保存在 ArmyList 中,客戶需要時可隨機獲取,其結構圖如圖 5 所示。
圖5 有限的多例模式的結構圖