天天看點

設計模式複習(二)-------單例模式

1.單例模式概述

  • 單例模式:確定一個類隻有一個執行個體,并提供一個全局通路點來通路點這個唯一執行個體。
  • 三個要點:
    • 某個類隻能有一個執行個體(單例)
    • 它必須自行建立這個執行個體(交給這個單例内部完成)
    • 它必須自行向整個系統提供這個執行個體(提供唯一的全局通路點)

2.單線程代碼實作:

/**
 * 	單線程單例模式
 *
 */
public class Singleton {
	
	//私有的對象執行個體
	private static Singleton instance = null;
	
	//私有構造函數
	private Singleton() {
		
	}		
	
	//全局通路點
	public static Singleton GetInstance() {
		//如果第一次通路,建立一個執行個體
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

           
/**
 * 測試類
 *
 */
public class test {

	public static void main(String[] args) {

		Singleton s1 = Singleton.GetInstance();
		Singleton s2 = Singleton.GetInstance();
		System.out.println(s1);
		System.out.println(s2);
		
	}
}

           

多線程同時建立這個執行個體時,會出現建立多個執行個體的情況,如下:

/**
 * 	單線程單例模式在多線程下出現問題
 *
 */
public class Singleton {
	
	//私有的執行個體
	private static Singleton instance = null;
	
	//私有構造函數
	private Singleton() {
		
	}		
	
	//全局通路點
	public static Singleton GetInstance() throws InterruptedException {
		//如果第一次通路,建立一個執行個體
		if(instance == null) {
            //為使效果更明顯,通過判斷條件後讓兩個睡眠3秒後,再建立執行個體
			Thread.sleep(3000);
			instance = new Singleton();
		}
		return instance;
	}
}
           
/**
 * 測試類
 *
 */
public class test {

	public static void main(String[] args) {
		Runnable r = () -> {
			Singleton getInstance = null;
			try {
				getInstance = Singleton.GetInstance();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(getInstance);
		};
        //兩個執行個體的位址不相同,說明建立了兩個執行個體
		Thread t1 = new Thread(r);
		Thread t2 = new Thread(r);
		t1.start();
		t2.start();
	}
}
           

3.多線程單例模式

如果我們讓類加載的時候就建立執行個體,靜态變量就被初始化,之後類的私有構造函數會被調用,單例類的唯一執行個體就被建立,這種建立方式就是餓漢式。

public class Singleton {
	
	//靜态變量初始化
	private static Singleton instance = new Singleton();
	
	//私有構造函數
	private Singleton() {}		
	
	//全局通路點
	public static Singleton GetInstance() {
		return instance;
	}
}

           

如果我們不在單例類加載時将自己執行個體化,而是把執行個體化推遲到

GetInstance()

方法調用的時候建立,這就叫做懶漢式,但在高并發、多線程環境下,如果有多個線程使用多個執行個體對象,懶漢式加載還是有可能建立多個執行個體對象的。

是以我們要使用一些進階語言特有的機制(如Java、C#中的

lock

關鍵字等等)來消除建立多個執行個體對象的可能性。

Java多線程

我們可以在整個

GetInstance()

方法上加鎖,這樣做是正确的,但是很笨重,比如有多個線程要通路

GetInstance()

方法,隻有一個線程能得到執行個體,而其他線程都要上鎖等待,效率比較低。

有一種方法可以解決這個問題:雙重檢查鎖定

/**
 * 	多線程懶加載
 *
 */
public class Singleton {
	
	//私有對象執行個體
	private static Singleton instance = null;
	
	//私有構造函數
	private Singleton() {}		
	
	//全局通路點
	public static Singleton GetInstance() {
        //檢查執行個體是否存在
		if(instance == null) {
			
			//檢查靜态方法的類鎖,互斥的通路臨界區
			synchronized (Singleton.class) {
				
				if(instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}
           

我們為什麼要判斷兩次呢?

第一次判斷:是為了判斷執行個體是否存在,如果不是第一次建立執行個體,直接就傳回建立好的執行個體,效率很好。而之前的直接對整個方法加鎖,不管是否已經建立好執行個體,都要不停的檢查類鎖。

第二次判斷:是為了互斥的通路臨界區,防止建立多個執行個體。

問:餓漢式單例和懶漢式單例的異同?

答:餓漢式單例類在類被加載時就将自己執行個體化,優點在于無須考慮多線程同時通路的問題,可以確定執行個體的唯一性,調用速度和反應時間更快,但是無論系統在運作的時候是否使用該單例對象,類一經加載,單例對象就需要建立,是以系統加載時間可能會加長; 懶漢式單例類則是在第一次單例類執行個體化的時候,因為涉及到雙重檢查鎖定等機制進行控制,資源初始化會耗費較長時間。

C#實作方式:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MultiThread
{
    /// <summary>
    /// 多線程單例模式示例
    /// </summary>
    public class Singleton
    {
        /// <summary>
        /// 唯一的執行個體對象,每次通路時重新讀取instance變量的值
        /// </summary>
        private static volatile Singleton instance = null;
        /*volatile修飾:編譯器在編譯代碼的時候會對代碼的順序進行微調,用volatile修飾保證了嚴格意義的順序。
          一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。
          精确地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用儲存在寄存器裡的備份。*/

        /// <summary>
        /// 輔助加鎖對象--注意:Static類型
        /// </summary>
        private static readonly object lockHelper = new object();

        /// <summary>
        /// 私有構造函數,防止外部應用使用new方法建立新的執行個體
        /// </summary>
        private Singleton()
        {
        }

        /// <summary>
        /// 擷取唯一的執行個體對象
        /// </summary>
        /// <returns>唯一的執行個體對象</returns>
        public static Singleton GetInstance()
        {
            if (instance == null) //先判斷對象是否存在,不存在時再加鎖處理--這樣做能夠保證執行效率!
            {
                lock (lockHelper) //加鎖--建立臨界區
                {
                    if (instance == null) //加鎖後二次判斷,隻允許一個線程判斷--有可能之前已有線程建立該對象
                    {
                        instance = new Singleton();
                    }
                }
            }

            return instance;
        }

        /// <summary>
        /// 單例類的其他接口
        /// </summary>
        public void ShowMe()
        {
            Console.WriteLine("Singleton pattern--MultiThread...");
        }

    }
}