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

設計模式-單例模式-Java


目錄




內容

1、單例模式的動機

  對於一個軟件系統的某些類而言,我們無須創建多個實例。舉個大家都熟知的例子——Windows任務管理器,如圖3-1所示,我們可以做一個這樣的嘗試,在Windows的“任務欄”的右鍵彈出菜單上多次點擊“啓動任務管理器”,看能否打開多個任務管理器窗口?如果你的桌面出現多個任務管理器,我請你喫飯,微笑(注:電腦中毒或私自修改Windows內核者除外)。通常情況下,無論我們啓動任務管理多少次,Wndows系統始終只能彈出一個任務管理器窗口,也就是說在一個Windows系統中,任務管理器存在唯一性。爲什麼要這樣設計呢?我們可以從以下兩個方面來分析:其一,如果能彈出多個窗口,且這些窗口的內容完全一致,全部是重複對象,這勢必會浪費系統資源,任務管理器需要獲取系統運行時的諸多信息,這些信息的獲取需要消耗一定的系統資源,包括CPU資源及內存資源等,浪費是可恥的,而且根本沒有必要顯示多個內容完全相同的窗口;其二,如果彈出的多個窗口內容不一致,問題就更加嚴重了,這意味着在某一瞬間系統資源使用情況和進程、服務等信息存在多個狀態,例如任務管理器窗口A顯示“CPU使用率”爲10%,窗口B顯示“CPU使用率”爲15%,到底哪個纔是真實的呢?這純屬“調戲”用戶,偷笑,給用戶帶來誤解,更不可取。由此可見,確保Windows任務管理器在系統中有且僅有一個非常重要。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gHRbWZ3s-1591444022017)(./images/taskManager.png)]
圖1-1 Windows任務管理器

  回到實際開發中,我們也經常遇到類似的情況,爲了節約系統資源,有時需要確保系統中某個類只有唯一一個實例,當這個唯一實例創建成功之後,,我們無法在創建一個同類型的其他對象,所有的操作都只能基於這個唯一實例。爲了確保對象的唯一性,我們可以通過單例模式來實現,這就是單例模式的動機所在。

2、單例模式概述

  下面我們來模擬實現Windows任務管理器,假設任務管理器類名爲:TaskManager,在TaskManager類中包含了大量的成員方法,例如構造函數TaskManager(),顯示進程的方法displayProcesses(),顯示服務的方法displayServices()等,該類的示意代碼如下2-1:

class TaskManager
{
	public TaskManager() {……} //初始化窗口
	public void displayProcesses() {……} //顯示進程
	public void displayServices() {……} //顯示服務
	……
}

  爲了實現Windows任務管理器的唯一性,我們通過如下三步來對該類進行重構:

  • (1)由於每次使用new關鍵字來實例化TaskManager類時都將產生一個新對象,爲了確保TaskManager實例的唯一性,我們需要禁止類的外部直接使用new來創建對象,因此需要將TaskManager的構造函數的可見性改爲private,如下代碼2-2所示:

      private TaskManager() {...}
    
  • (2)將構造函數改爲private修飾後該如何創建對象呢?不要着急,雖然類的外部無法在使用new來創建對象,但是在TaskManager的內部還是可以創建的,可見性只對類外有效。因此,我們可以在TaskManager中創建並保存這個唯一實例。爲了讓外界可以訪問這個唯一實例,需要在TaskManager中定義一個靜態的TaskManager類型的私有成員變量,如下代碼2-3所示:

      private static TaskManager tm = null;
    
  • (3)爲了保證成員變量的封裝性,我們將TaskManager類型的對象的可見性設置爲private,但外界該如何使用該成員變量並何時實例化該成員變量呢?答案是增加一個公有的靜態方法,如下代碼2-4所示:

      public static TaskManager getInstance()
      {
      	if (tm == null)
      	{
      		tm = new TaskManager();
      	}
      	return tm;
      }
    

  在getInstance()方法中首先判斷tm對象是否存在,如果不存在(即tm == null ),則使用new 關鍵字創建一個新的TaskManager類型的對象,在返回新創建的tm對象;否則直接返回已有的tm對象。
  需要注意的是getInstance()方法的修飾符,首先它應該是一個public 方法,以便供外界其他對象使用,其次它使用了static關鍵字,即他是一個靜態方法,在類外可以直接通過類名來訪問,而無須創建TaskManager對象,事實上在類外也無法創建TaskManager對象,以爲構造函數是私有的。

  通過以上三個步驟,我們完成了一個最簡單的單例類的設計,其完整代碼2-5如下:

class TaskManager
{
	private static TaskManager tm = null;
	private TaskManager() {……} //初始化窗口
	public void displayProcesses() {……} //顯示進程
	public void displayServices() {……} //顯示服務
	public static TaskManager getInstance()
	{
		if (tm == null)
		{	
			tm = new TaskManager();
		}
		return tm;
	}
	……
}

  在類外我們無法直接創建新的TaskManager對象,但可以通過代碼TaskManager.getInstance()來訪問實例對象,第一次調用getInstance()方法是將創建唯一實例,再次調用將返回第一次創建的實例,從而確保實例對象的唯一性。

  上述代碼也是單例模式的一種最典型的實現方式,有了以上基礎,理解單例模式的定義和結構就非常容易了。到單例模式定義如下:

2.1、單例模式定義

  單例模式(Singleton Pattern):確保某一個類只有一個實例,而且自行實例化並向整個系統提供者個實例,整個類稱爲單例類,它提供全局風味的方法。單例模式是一種對象創建型模式。

2.2、單例模式要點

  單例模式有三個要點:

  1. 某個類只能有一個實例
  2. 它必須自行創建這個實例
  3. 它必須自行向整個系統提供這個實例

2.3、單例模式結構圖中角色

  單例模式是結構最簡單的設計模式之一,在它的核心結構中只包含一個被稱爲單例類的特殊類。單例模式結構圖2.3-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-e8W9mttf-1591444022019)(./images/model_singleton.png)]
圖2.3-1單例模式結構圖

  單例模式結構圖中只包含一個單例角色:

  • Singleton:在單例類的內部實現只生產一個實例,同時它提供一個靜態的getInstance()工廠方法,讓客戶可以訪問它的唯一實例;爲了放在外部對其實例化,將其構造方法設計爲私有;在單例類內部定義了一個Singleton類型的靜態對象,作爲外部共享的唯一實例對象。

3、負載均衡器的設計與實現

  Sunny軟件公司承接了一個服務器負載均衡(Load Balance)燃盡的開發工作,該軟件運行在一臺負載均衡器上,可以將併發訪問和數據流量分發到拂去集羣中的多臺設備上進行併發處理,提高系統的整體處理能力,縮短響應時間。由於集羣中的服務器需要動態刪減,且客戶端請求需要統一分發,因此需要確保負載均衡器的唯一性,只能有一個負載均衡器來負責拂去的管理和請求的分發,否則將會帶來服務器狀態的不一致以及請求分配衝突等問題。如何確保負載均衡器的唯一性是該軟件成功的關鍵。

  Sunny公司開發人員通過分析和權衡,決定使用單例模式來設計該負載均衡器,結構圖如圖3-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tFlMKUOW-1591444022021)(./images/loadbalance.png)]
  在圖3-1中,將負載均衡器LoadBalance設計爲單例類,其中包含一個存儲拂去信息的集合serverList,每次在serverList中隨機選擇一臺服務器來響應客戶端的請求,

  • 負載均衡器實現代碼3-1如下所示:

      package singleton;
    
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Random;
    
      // 負載均衡器
      public class LoadBalance {
      	// 私有靜態成員變量,存儲唯一實例
      	private static LoadBalance instance = null;
      	// 服務器集合
      	private List<String> serverList = null;
    
      	// 私有構造方法
      	private LoadBalance() {
      		serverList = new ArrayList<>();
      	}
    
      	// 公有靜態成員方法,返回唯一實例
      	public static LoadBalance getLoadBalance() {
      		if(instance == null)
      			instance = new LoadBalance();
      		return instance;
      	}
    
      	// 添加服務器
      	public void addServer(String server) {
      		serverList.add(server);
      	}
    
      	// 移除服務器
      	public void remove(String server) {
      		serverList.remove(server);
      	}
    
      	// 隨機獲取服務器
      	public String getServer() {
      		return serverList.get(new Random().nextInt(serverList.size()));
      	}
      }
    
  • 客戶端測試代碼3-2:

      package singleton;
    
      public class TestLoadBalance {
      	public static void main(String[] args) {
      		LoadBalance balance1, balance2, balance3, balance4;
      		balance1 = LoadBalance.getLoadBalance();
      		balance2 = LoadBalance.getLoadBalance();
      		balance3 = LoadBalance.getLoadBalance();
      		balance4 = LoadBalance.getLoadBalance();
    
      		//判斷服務器負載均衡器是否同一個
      		if(balance1 == balance2 && balance1 == balance3 && balance1 == balance4)
      			System.out.println("服務器負載均衡器具有唯一性!");
    
      		// 添加服務器
      		balance1.addServer("server-1");
      		balance1.addServer("server-2");
      		balance1.addServer("server-3");
      		balance1.addServer("server-4");
    
      		// 模擬客戶端請求分發
      		String server = null;
      		for(int i = 0; i < 10; i++) {
      			server = balance1.getServer();
      			System.out.println("請求分發至服務器:" + server);
      		}
      	}
      }
    
  • 測試結果:

      服務器負載均衡器具有唯一性!
      請求分發至服務器:server-4
      請求分發至服務器:server-1
      請求分發至服務器:server-4
      請求分發至服務器:server-3
      請求分發至服務器:server-3
      請求分發至服務器:server-1
      請求分發至服務器:server-1
      請求分發至服務器:server-3
      請求分發至服務器:server-4
      請求分發至服務器:server-3
    

4、餓漢式單例與懶漢式單例

  Sunny公司開發人員使用單例模式實現了負載均衡器的設計,但是在實際使用中處理了一個非常嚴重的問題,當負載均衡器在啓動過程中用戶再次啓動該負載均衡器時,系統無任何異常,但當客戶端提交請求時出現請求分發失敗,通過仔細分析發現原來系統中換上存在多個負載均衡器對象,導致分發時目標服務器不一致,從而產生衝突。爲什麼會這樣呢?Sunny公司開發人員百思不得其解。

  現在我們對負載均衡器的實現代碼再次分析,當第一次調用getLoadBalance()方法創建並啓動負載均衡器時,instance對象爲null值,因此係統將執行代碼instance = new LoadBalance(),在此過程中,由於要對LoadBalance進行大量的初始化工作,需要一段實際來創建LoadBalance對象。而在此時,如果再一次調用getLoadBalance()方法(通常發生在多線程環境中),由於instance此時爲null,因此代碼instance = new LoadBalance();再次執行,導致最終創建了多個instance對象,這違背了單例模式的初衷,也導致系統運行發生錯誤。

  如何解決問題?我們至少有兩種解決方案,在正式介紹這兩種解決方案之前,先介紹一下單例類的兩種不同實現方式,餓漢式單例類和懶漢式單例類。

4.1、餓漢式單例類

  餓漢式單例類是實現起來最簡單的單例類,餓漢式單例類結構圖如圖4.1-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6TZzQNZk-1591444022022)(./images/eagerSingleton.png)]
圖4.1-1 餓漢式單例類結構圖

  從圖4.1-1中可以看出,由於在定義靜態成員變量時實例化單例類,因此在類加載的時候就已經創建了單例對象,代碼4.1-1如下所示:

class EagerSingleton {
	private static final EagerSingleton instance = new EagerSingleton();
	private EagerSingleton() { }
	public static EagerSingleton getInstance() {
		return instance;
	}
}

  當類被加載時,靜態變量instance會被初始化,此時類的私有構造方法會被調用,單例類的唯一實例將被創建。如果使用餓漢式單例來實現負載均衡器LoadBalance類的設計,則不會出現創建多個單例對象的情況,可確保單例對象的唯一性。

4.2、懶漢式單例類與線程鎖定

  除了餓漢式單例,還有一種經典的懶漢式單例,也就是前面的負載均衡器LoadBalance類的實現方式。懶漢式單例類結構圖如圖4.2-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GPKzKpjY-1591444022024)(./images/lazySingleton.png)]
圖4.2-1 懶漢式單例結構圖

  從圖4.2-1中可以看出,懶漢式單例在第一次調用getInstance()方法時實例化,在類加載時並不自行實例化,這種技術又稱爲延遲加載(Lazy Load)技術,即需要的時候在加載實例,爲了避免多個線程調用getInstance()方法,我們可以使用關鍵字synchronized,代碼4.2-1如下所示:

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

  該懶漢式單例類在getInstance()方法前面增加了關鍵字synchronized添加線程鎖,以處理多個線程同時訪問的問題。但是,上述代碼雖然解決了線程安全問題,但是每次調用getInstance()都需要進行線程鎖定判斷,在多線程高併發訪問環境胡中,將會導致系統性能大大降低。如何既解決線程安全問題又不影響系統性能呢?我們繼續對懶漢式單例進行改進。事實上,我們無須對整個getInstance()方法進行鎖定,只需對其中的代碼“instance = new LazySingleton();"進行鎖定即可。因此getInstance()方法可以進行如下改造代碼4.2-2:

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

  問題貌似得以解決,事實並非如此。如果使用以上代碼來實現單例,還是會存在單例對象不唯一。原因如下:
假如在某一瞬間線程A和線程B都在調用getInstance()方法,此時instance對象爲null值,均能通過instance == null的判斷。由於實現了synchronized加鎖機制,線程A進入synchronized鎖定的代
碼中執行實例創建代碼,線程B處於排隊等待狀態,必須等待線程A執行完畢後纔可以進入synchronized鎖定代碼。但當A執行完畢時,線程B並不知道實例已經創建,將繼續創建新的實例,導致產生多個單例對象,違背單例模式的設計思想,因此需要進行進一步改進,在
synchronized中再進行一次(instance == null)判斷,這種方式稱爲雙重檢查鎖定(Double-CheckLocking)。使用雙重檢查鎖定實現的懶漢式單例類完整代碼4.2-3如下所示:

class LazySingleton {
	private volatile static LazySingleton instance = null;
	private LazySingleton() { }
	public static LazySingleton getInstance() {
		//第一重判斷
		if (instance == null) {
		//鎖定代碼塊
			synchronized (LazySingleton.class) {
				//第二重判斷
				if (instance == null) {
					instance = new LazySingleton(); //創建單例實例
				}
			}
		}
		return instance;
	}
}

  需要注意的是,如果使用雙重檢查鎖定來實現懶漢式單例類,需要在靜態成員變量instance之前增加修飾符volatile,被volatile修飾的成員變量可以確保多個線程都能夠正確處理,且該代
碼只能在JDK 1.5及以上版本中才能正確執行。由於volatile關鍵字會屏蔽Java虛擬機所做的一些代碼優化,可能會導致系統運行效率降低,因此即使使用雙重檢查鎖定來實現單例模式也不是一種完美的實現方式。

4.3、而函數單例類與懶漢式單例類比較

  餓漢式單例類在類被加載時就將自己實例化,它的優點在於無須考慮多線程訪問問題,可以確保實例的唯一性;從調用速度和反應時間角度來講,由於單例對象一開始就得以創建,因此要優於懶漢式單例。但是無論系統在運行時是否需要使用該單例對象,由於在類加載時該對象就需要創建,因此從資源利用效率角度來講,餓漢式單例不及懶漢式單例,而且在系統加載時由於需要創建餓漢式單例對象,加載時間可能會比較長。

  懶漢式單例類在第一次使用時創建,無須一直佔用系統資源,實現了延遲加載,但是必須處理好多個線程同時訪問的問題,特別是當單例類作爲資源控制器,在實例化時必然涉及資源
初始化,而資源初始化很有可能耗費大量時間,這意味着出現多線程同時首次引用此類的機率變得較大,需要通過雙重檢查鎖定等機制進行控制,這將導致系統性能受到一定影響。

5、IoDH:一種更好的單例實現方法

  餓漢式單例類不能實現延遲加載,不管將來用不用始終佔據內存;懶漢式單例類線程安全控制煩瑣,而且性能受影響。可見,無論是餓漢式單例還是懶漢式單例都存在這樣那樣的問題,有沒有一種方法,能夠將兩種單例的缺點都克服,而將兩者的優點合二爲一呢?答案是:Yes!下面我們來學習這種更好的被稱之爲Initialization Demand Holder (IoDH)的技術。

  在IoDH中,我們在單例類中增加一個靜態(static)內部類,在該內部類中創建單例對象,再將該單例對象通過getInstance()方法返回給外部使用,實現代碼5-1如下所示:

//Initialization on Demand Holder
class Singleton {
	private Singleton() {
	}
	
	private static class HolderClass {
		private fianl static Singleton instance = new Singleton();
	}
	
	public static Singleton getInstance() {
		return HolderClass.instance;
	}
	
	public static void main(String[] args) {
		Singleton s1, s2;
		s1 = Singleton.getInstance();
		s2 = Singleton.getInstance();
		
		System.out.println(s1 == s2);
	}
}

  編譯並運行上述代碼,運行結果爲:true,即創建的單例對象s1和s2爲同一對象。由於靜態單例對象沒有作爲Singleton的成員變量直接實例化,因此類加載時不會實例化Singleton,第一次
調用getInstance()時將加載內部類HolderClass,在該內部類中定義了一個static類型的變量instance,此時會首先初始化這個成員變量,由Java虛擬機來保證其線程安全性,確保該成員變量只能初始化一次。由於getInstance()方法沒有任何線程鎖定,因此其性能不會造成任何影響。

  通過使用IoDH,我們既可以實現延遲加載,又可以保證線程安全,不影響系統性能,不失爲一種最好的Java語言單例模式實現方式(其缺點是與編程語言本身的特性相關,很多面向對象
語言不支持IoDH)。

6、總結

6.1、優缺點

  單例模式作爲一種目標明確、結構簡單、理解容易的設計模式,在軟件開發中使用頻率相當高,在很多應用軟件和框架中都得以廣泛應用。

  • 注意優點

    • (1) 單例模式提供了對唯一實例的受控訪問。因爲單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它。
    • (2) 由於在系統內存中只存在一個對象,因此可以節約系統資源,對於一些需要頻繁創建和銷燬的對象單例模式無疑可以提高系統的性能。
    • (3) 允許可變數目的實例。基於單例模式我們可以進行擴展,使用與單例控制相似的方法來獲得指定
  • 主要缺點:

    • (1) 由於單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
    • (2) 單例類的職責過重,在一定程度上違背了“單一職責原則”。因爲單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的創建和產品的本身的功能融合到一起。
    • (3) 現在很多面向對象語言(如Java、C#)的運行環境都提供了自動垃圾回收的技術,因此,如果實例化的共享對象長時間不被利用,系統會認爲它是垃圾,會自動銷燬並回收資源,下次利用時又將重新實例化,這將導致共享的單例對象狀態的丟失。

6.2、適用場景

  在以下情況下可以考慮使用單例模式:

  • (1) 系統只需要一個實例對象,如系統要求提供一個唯一的序列號生成器或資源管理器,或者需要考慮資源消耗太大而只允許創建一個對象。
  • (2) 客戶調用類的單個實例只允許使用一個公共訪問點,除了該公共訪問點,不能通過其他途徑訪問該實例。

7、案例

7.1、三種方式實現負載均衡器

7.1.1、帶雙重檢查鎖定機制的懶漢式

  • 負載均衡器代碼7.1.1-1如下:

      package singleton;
    
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Random;
    
      public class LazySingleton {
      	// 私有靜態成員變量,存儲唯一實例
      		private static LazySingleton instance = null;
      		// 服務器集合
      		private List<String> serverList = null;
    
      		// 私有構造方法
      		private LazySingleton() {
      			serverList = new ArrayList<>();
      		}
    
      		// 公有靜態成員方法,返回唯一實例
      		public static LazySingleton getLoadBalance() {
      			if(instance == null) {
      				synchronized(LazySingleton.class) {
      					if(instance == null)
      						instance = new LazySingleton();
      				}
      			}
      			return instance;
      		}
    
      		// 添加服務器
      		public void addServer(String server) {
      			serverList.add(server);
      		}
    
      		// 移除服務器
      		public void remove(String server) {
      			serverList.remove(server);
      		}
    
      		// 隨機獲取服務器
      		public String getServer() {
      			return serverList.get(new Random().nextInt(serverList.size()));
      		}
      }
    
  • 測試代碼7.1.1-2如下:

      package singleton;
    
      public class TestLazySingleton {
      	public static void main(String[] args) {
      		LazySingleton balance1, balance2, balance3, balance4;
      		balance1 = LazySingleton.getLoadBalance();
      		balance2 = LazySingleton.getLoadBalance();
      		balance3 = LazySingleton.getLoadBalance();
      		balance4 = LazySingleton.getLoadBalance();
    
      		//判斷服務器負載均衡器是否同一個
      		if(balance1 == balance2 && balance1 == balance3 && balance1 == balance4)
      			System.out.println("服務器負載均衡器具有唯一性!");
    
      		// 添加服務器
      		balance1.addServer("server-1");
      		balance1.addServer("server-2");
      		balance1.addServer("server-3");
      		balance1.addServer("server-4");
    
      		// 模擬客戶端請求分發
      		String server = null;
      		for(int i = 0; i < 10; i++) {
      			server = balance1.getServer();
      			System.out.println("請求分發至服務器:" + server);
      		}
      	}
      }
    
  • 測試結果:

      服務器負載均衡器具有唯一性!
      請求分發至服務器:server-1
      請求分發至服務器:server-4
      請求分發至服務器:server-1
      請求分發至服務器:server-2
      請求分發至服務器:server-1
      請求分發至服務器:server-3
      請求分發至服務器:server-4
      請求分發至服務器:server-1
      請求分發至服務器:server-1
      請求分發至服務器:server-1
    

7.1.2、 餓漢式

  • 負載均衡器代碼7.1.2-1代碼如下:

      // 負載均衡器
      package singleton;
    
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Random;
    
      public class EagerLoadBalance {
      	// 私有靜態成員變量,存儲唯一實例
      		private static EagerLoadBalance instance = new EagerLoadBalance();
      		// 服務器集合
      		private List<String> serverList = null;
    
      		// 私有構造方法
      		private EagerLoadBalance() {
      			serverList = new ArrayList<>();
      		}
    
      		// 公有靜態成員方法,返回唯一實例
      		public static EagerLoadBalance getLoadBalance() {
      			return instance;
      		}
    
      		// 添加服務器
      		public void addServer(String server) {
      			serverList.add(server);
      		}
    
      		// 移除服務器
      		public void remove(String server) {
      			serverList.remove(server);
      		}
    
      		// 隨機獲取服務器
      		public String getServer() {
      			return serverList.get(new Random().nextInt(serverList.size()));
      		}
      }
    
  • 測試類代碼7.1.2-2如下:

      package singleton;
    
      public class TestEagerSingleton {
      	public static void main(String[] args) {
      		EagerLoadBalance balance1, balance2, balance3, balance4;
      		balance1 = EagerLoadBalance.getLoadBalance();
      		balance2 = EagerLoadBalance.getLoadBalance();
      		balance3 = EagerLoadBalance.getLoadBalance();
      		balance4 = EagerLoadBalance.getLoadBalance();
    
      		//判斷服務器負載均衡器是否同一個
      		if(balance1 == balance2 && balance1 == balance3 && balance1 == balance4)
      			System.out.println("服務器負載均衡器具有唯一性!");
    
      		// 添加服務器
      		balance1.addServer("server-1");
      		balance1.addServer("server-2");
      		balance1.addServer("server-3");
      		balance1.addServer("server-4");
    
      		// 模擬客戶端請求分發
      		String server = null;
      		for(int i = 0; i < 10; i++) {
      			server = balance1.getServer();
      			System.out.println("請求分發至服務器:" + server);
      		}
      	}
      }
      測試結果:
      服務器負載均衡器具有唯一性!
      請求分發至服務器:server-4
      請求分發至服務器:server-3
      請求分發至服務器:server-1
      請求分發至服務器:server-4
      請求分發至服務器:server-4
      請求分發至服務器:server-3
      請求分發至服務器:server-1
      請求分發至服務器:server-4
      請求分發至服務器:server-1
      請求分發至服務器:server-4
    

7.1.3、IoDH

  • 負載均衡器代碼7.1.3-1:

      package singleton;
    
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Random;
    
      public class IoDHLoadBalance {
    
      		// 服務器集合
      		private List<String> serverList = null;
    
      		// 私有構造方法
      		private IoDHLoadBalance() {
      			serverList = new ArrayList<>();
      		}
    
      		// 私有內部類,延遲生成唯一對象
      		private static  class HolderClass {
      			private final static IoDHLoadBalance instance = new IoDHLoadBalance();
      		}
    
      		// 公有靜態成員方法,返回唯一實例
      		public static IoDHLoadBalance getLoadBalance() {
      			return HolderClass.instance;
      		}
      		// 添加服務器
      		public void addServer(String server) {
      			serverList.add(server);
      		}
    
      		// 移除服務器
      		public void remove(String server) {
      			serverList.remove(server);
      		}
    
      		// 隨機獲取服務器
      		public String getServer() {
      			return serverList.get(new Random().nextInt(serverList.size()));
      		}
      }
    
  • 測試類代碼7.1.3-2:

      package singleton;
    
      public class TestIoDHSingleton {
      	public static void main(String[] args) {
      		IoDHLoadBalance balance1, balance2, balance3, balance4;
      		balance1 = IoDHLoadBalance.getLoadBalance();
      		balance2 = IoDHLoadBalance.getLoadBalance();
      		balance3 = IoDHLoadBalance.getLoadBalance();
      		balance4 = IoDHLoadBalance.getLoadBalance();
    
      		//判斷服務器負載均衡器是否同一個
      		if(balance1 == balance2 && balance1 == balance3 && balance1 == balance4)
      			System.out.println("服務器負載均衡器具有唯一性!");
    
      		// 添加服務器
      		balance1.addServer("server-1");
      		balance1.addServer("server-2");
      		balance1.addServer("server-3");
      		balance1.addServer("server-4");
    
      		// 模擬客戶端請求分發
      		String server = null;
      		for(int i = 0; i < 5; i++) {
      			server = balance1.getServer();
      			System.out.println("請求分發至服務器:" + server);
      		}
      	}
      }
    
  • 測試結果:

      服務器負載均衡器具有唯一性!
      請求分發至服務器:server-3
      請求分發至服務器:server-2
      請求分發至服務器:server-2
      請求分發至服務器:server-4
      請求分發至服務器:server-4
    

後記

  參考文獻:Java設計模式(劉偉).pdf。持續更新,歡迎交流,本人QQ:806797785

前端項目源代碼地址:https://gitee.com/gaogzhen/vue-leyou
後端JAVA源代碼地址:https://gitee.com/gaogzhen/JAVA
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章