GOF23之單例模式

核心作用:

保證一個類只有一個實例,並且提供一個訪問該實例的全局訪問點

就是整個程序有且僅有一個實例。該類負責創建自己的對象,同時確保只有一個對象被創建。在Java,一般常用在工具類的實現或創建對象需要消耗資源。

常見應用場景:

1、任務管理器
2、回收站
3、網站計數器
4、數據庫連接池

優點:

1、單例模式只能產生一個實例,減少了系統性能的開銷,當一個對象的產生需要比較多的資源時,如“讀取配置,產生其他依賴對象”可以通過在應用啓動時產生一個單例對象,然後永久駐留內存
2、單例模式可以在系統設置一個全局訪問點,優化環境共享資源訪問。

常見的五種單例模式實現方式:

餓漢式(線程安全、調用效率高、不支持延時加載)
懶漢式(線程安全、調用效率低、支持延時加載)
雙重檢查鎖式 (由於JVM底層內部模型原因,偶爾出現問題,不建議使用,但可用voletile修飾實例變量解決問題)
靜態內部類(線程安全、調用效率搞、支持延時加載)
枚舉單例(線程安全、調用效率高、不支持延時加載)

一、餓漢式實現(單例對象立即加載)

package com.hezeu.singleton;
/**
*@ClassnameSingletonDemo01
*@Description 測試餓漢式單例模式
*@Date2020/2/19下午03:48
*@Createdby朱進博 [email protected]
*/
public class SingletonDemo01{

	private static SingletonDemo01instance = new SingletonDemo01();

	private SingletonDemo01(){}

	publicstatic SingletonDemo01 getInstance(){
		return instance;
	}
}

優點:餓漢式單例模式代碼中,static變量會在類裝載時初始化,此時也不會設計多個線程對象訪問該對象的問題,虛擬機保證只會加再一次類,不會發生併發訪問。 問題:如果只是加載類,不調用getInstance()方法,會造成資源浪費

二、懶漢式實現(單例對象延遲加載)

package com.hezeu.singleton;
/**
*@ClassnameSingletonDemo02
*@Description懶漢式單例模式
*@Date2020/2/19下午03:48
*@Createdby朱進博
*/
public class SingletonDemo02{

	private static SingletonDemo02instance;

	private SingletonDemo02(){}

	public static synchronized SingletonDemo02 getInstance(){
		if(instance==null){
			instance=newSingletonDemo02();
		}
		returninstance;
	}
}

優點:Lazy load! 延遲加載,懶加載,用時再加載
缺點:資源利用率高了,但是,每次調用getInstance()時都會執行Synchronized同步,併發效率低

三、雙重檢查鎖式實現(不建議使用)

package com.hezeu.singleton;

/**
*@Classname Singleton03
*@Description 雙重檢查鎖機制
*@Date 2020/2/19下午04:03
*@Created by朱進博 [email protected]
*/
public class SingletonDemo03{
	private static SingletonDemo03 instance =  null;

	private SingletonDemo03(){}

	public static SingletonDemo03 getInstance(){
		if(instance==null){
			SingletonDemo03sc=null;
			synchronized(SingletonDemo03.class){
				if(sc==null){
					synchronized(SingletonDemo03.class){
						if(sc==null){
						sc=newSingletonDemo03();
						}
					}
					instance=sc;
				}
			}
		}
		return instance;
	}
}

優點:雙重檢查鎖式將同步內容下放到if內部,提高了執行的效率,不必每次獲取對象時都同步,只有第一次才同步。
缺點:由於編譯器優化問題和JVM底層內部模型原型,偶爾出現問題

優化版:

package com.hezeu.singleton;

/**
*@Classname SingletonDemo07
*@Description 優化雙重檢查鎖式
*@Date 2020/2/20下午11:05
*@Created by 朱進博 [email protected]
*/
public class SingletonDemo07{
	private static volatile SingletonDemo07 instance;

	private SingletonDemo07(){}

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

雙重檢查鎖模式進行了兩次判斷,第一次避免不要的實例,第二次爲了進行同步,並避免了多線程問題,又由於實例化對象創建可能出現JVM重排,在多線程訪問存在風險,因此使用volatile修飾instance實例變量,解決該問題

四、靜態內部類式實現(線程安全、效率高、支持延時加載,也是一種懶加載)

package com.hezeu.singleton;

/**
*@Classname SingletonDemo04
*@Description 靜態內部類實現單例模式
*@Date 2020/2/19 下午04:09
*@Created by 朱進博 [email protected]
*/
public class SingletonDemo04{
	private static class SingletonClassInstance{
		private static final SingletonDemo04 instance = new SingletonDemo04();
	}
	public static SingletonDemo04 getInstance(){
		Return SingletonClassInstance.instance;
	}
	private SingletonDemo04(){}
}

要點:外部類沒有static屬性,不會像餓漢式那樣立即加載對象
優點:只有真正調用getInstance(),纔會加載靜態內部類,加載類時是線程安全的。
Instance是static final類型,保證了內存中只有這樣一個實例存在,而且只能賦值一次,從而保證了線程安全。

五、枚舉實現單例模式

package com.hezeu.singleton;

/**
*@Classname SingletonDemo05
*@Description 枚舉實現單例模式
*@Date 2020/2/19 下午04:14
*@Created by 朱進博 [email protected]
*/
public enum SingletonDemo05{
	INTEGER;//枚舉元素,代表一個Singleton實例

	//自己的操作
	public void singletonOperation(){
	//功能處理
	}
}

優點:實現簡單
枚舉本身就是單例。JVM底層提供保障,避免通過反射和序列化的漏洞!
缺點:不支持延時加載

在這裏插入圖片描述

反射、反序列化破解單例模式:

package com.hezeu.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
*@Classname Client02
*@Description 測試反射和反序列化破解單例模式
*@Date 2020/2/19 下午04:20
*@Created by 朱進博 [email protected]
*/
public class Client02{
	public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException,IllegalAccessException,InvocationTargetException,InstantiationException,IOException{
		SingletonDemo06s1=SingletonDemo06.getInstance();
		SingletonDemo06s2=SingletonDemo06.getInstance();

		Classclazz = Class.forName("com.hezeu.singleton.SingletonDemo06");
		Constructor<SingletonDemo06> c = clazz.getDeclaredConstructor(null);
		c.setAccessible(true);
		SingletonDemo06s3 = (SingletonDemo06)c.newInstance();
		System.out.println(s3==s2);

		FileOutputStream fos = new FileOutputStream("f:/1.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(s1);
		oos.flush();
		oos.close();
			
		ObjectInputStreamo is = new ObjectInputStream(new FileInputStream("F:/1.txt"));
		SingletonDemo06s4 = (SingletonDemo06)ois.readObject();
		System.out.println(s4==s1);
	}
}

解決方案:
反射:可以在構造方法中手動拋出異常,判斷如果已經實例化則拋出異常
反序列化:可以通過定義readResolve()防止獲得不同對象。
----反序列化時,如果對象所在類定義了readResolve,定義返回那個對象

單例模式效率測試:

package com.hezeu.singleton;

import java.util.concurrent.CountDownLatch;

/**
*@Classname Client03
*@Description 測試單例模式效率
*@Date 2020/2/19 下午 04:38
*@Createdby 朱進博 [email protected]
*/
public class Client03{
	public static void main(String[] args) throws InterruptedException{
		int threadNum=10;
		CountDownLatch countDownLatch = new CountDownLatch(threadNum);
		long start = System.currentTimeMillis();
		for(inti=0;i<threadNum;i++){
			new Thread(new Runnable(){
				@Override
				public void run(){
					for(inti=0;i<11000000;i++){
					//SingletonDemo01instance=SingletonDemo01.getInstance();//餓漢式47
					//SingletonDemo02instance=SingletonDemo02.getInstance();//懶漢式3816
					//SingletonDemo04instance=SingletonDemo04.getInstance();//靜態內部類18
					//SingletonDemo05instance=SingletonDemo05.INSTANCE;//枚舉46
					//SingletonDemo07instance=SingletonDemo07.getInstance();//雙重檢查鎖36
					}
					countDownLatch.countDown();
				}
			}).start();
		}
		countDownLatch.await();
		Long end=System.currentTimeMillis();
		System.out.println("時間--->"+(end-start));
	
	}
}

餓漢式 47ms
懶漢式 3816ms
靜態內部類 18ms
枚舉式 46ms
靜態內部類 18ms
雙重檢查鎖式 36ms

CountDownLatch:
同步輔助類,在完成一組正在其他線程中執行操作之前,它允許一個或多個線程一直等待 countDown()
當前線程調用此方法,則計數減一 Await() 調用此方法會一直阻塞當前線程,直到計時器值爲0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章