什麼是單例
單例模式確保某各類只有一個實例,而且自行實例化並向整個系統提供這個實例。在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具有資源管理器的功能,每臺計算機可以有若干個打印機,但只能有一個Printer spooler,以避免兩個打印作業同時輸出到打印機中,每臺計算機可以有若干通信端口,系統應當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調用。總之,選擇單例模式就是爲了避免不一致狀態
單例模式特點
1、單例類只能有一個實例。
2、單例類必須自己創建自己唯一的實例。
3、單例類必須給所有其它對象提供這一實例。
單例模式優缺點
1、單例類只有一個實例
2、共享資源,全局使用
3、節省創建時間,提高性能
單例模式的七種寫法
分別是「餓漢」、「懶漢(非線程安全)」、「懶漢(線程安全)」、「雙重校驗鎖」、「靜態內部類」、「枚舉」和「容器類管理]
1.餓漢式
package com.xuyu.V1;
/**
* author:須臾
*/
public class SingletonV1 {
/**
* 餓漢式
* 優點:先天線程安全,當類初始化的時候就會創建該對象
* 缺點:如果餓漢式使用頻繁,可能會影響項目啓動效率
*/
private static SingletonV1 singletonV1=new SingletonV1();
/**
* 將構造函數私有化,禁止初始化
*/
private SingletonV1(){}
public static SingletonV1 getInstance(){
return singletonV1;
}
/**
* 測試單例
*/
public static void main(String[] args) {
SingletonV1 instance1 = SingletonV1.getInstance();
SingletonV1 instance2 = SingletonV1.getInstance();
//結果爲true,說明保證了單例
System.out.println(instance1==instance2);
}
}
源碼分析Runtime
//餓漢式單例
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
..
}
2.懶漢式(線程不安全)
package com.xuyu.V2;
/**
* author:須臾
*/
public class SingletonV2 {
/**
* 餓漢式(線程不安全)
*/
private static SingletonV2 singletonV2;
private SingletonV2(){}
/**
* 創建對象使用
*/
public static SingletonV2 getInstance(){
if(singletonV2==null){
try {
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
singletonV2=new SingletonV2();
}
return singletonV2;
}
/**
* 測試單例
*/
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(new Runnable() {
public void run() {
SingletonV2 instance1 = SingletonV2.getInstance();
System.out.println(Thread.currentThread().getName()+","+instance1);
}
}).start();
}
}
}
輸出結果:線程不安全
Thread-1,com.xuyu.V2.SingletonV2@383a0ba
Thread-4,com.xuyu.V2.SingletonV2@d9d8ad0
Thread-0,com.xuyu.V2.SingletonV2@546431f0
Thread-5,com.xuyu.V2.SingletonV2@2858c11c
Thread-22,com.xuyu.V2.SingletonV2@3635f62a
Thread-6,com.xuyu.V2.SingletonV2@48369750
Thread-7,com.xuyu.V2.SingletonV2@2770f418
Thread-3,com.xuyu.V2.SingletonV2@6d9da26a
Thread-13,com.xuyu.V2.SingletonV2@77355386
Thread-10,com.xuyu.V2.SingletonV2@29580e2d
....
Thread-94,com.xuyu.V2.SingletonV2@3945e031
Thread-91,com.xuyu.V2.SingletonV2@5caf9db6
3.懶漢式(線程安全)
package com.xuyu.V3;
/**
* author:須臾
*/
public class SingletonV3 {
/**
* 懶漢式線程安全
*/
private static SingletonV3 singletonV3;
private SingletonV3(){}
/**
* 效率低
*/
public synchronized static SingletonV3 getInstance(){
try {
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
if (singletonV3==null){
System.out.println("創建實例SingletonV3");
singletonV3=new SingletonV3();
}
System.out.println("獲取SingletonV3實例");
return singletonV3;
}
/**
* 測試單例
*/
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(new Runnable() {
public void run() {
SingletonV3 instance1 = SingletonV3.getInstance();
System.out.println(Thread.currentThread().getName()+","+instance1);
}
}).start();
}
}
}
輸出結果
創建實例SingletonV3
獲取SingletonV3實例
Thread-0,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3實例
Thread-99,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3實例
Thread-98,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3實例
Thread-97,com.xuyu.V3.SingletonV3@95458f7
獲取SingletonV3實例
....
4.雙重檢驗鎖(DCL)
package com.xuyu.V4;
public class SingletonV4 {
/**
* volatile 禁止指令重排序
*/
private static volatile SingletonV4 singletonV4;
private SingletonV4(){}
public static SingletonV4 getInstance(){
if(singletonV4==null){//第一次判斷如果沒有創建對象就開始加鎖
synchronized (SingletonV4.class){
if (singletonV4==null){//當用戶搶到鎖,判斷初始化
System.out.println("第一次開始創建實例對象,獲取到鎖了");
try {
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
singletonV4=new SingletonV4();
}
}
}
return singletonV4;
}
/**
* 測試單例
*/
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(new Runnable() {
public void run() {
SingletonV4 instance1 = SingletonV4.getInstance();
System.out.println(Thread.currentThread().getName()+","+instance1);
}
}).start();
}
}
}
輸出結果:線程安全
第一次開始創建實例對象,獲取到鎖了
Thread-99,com.xuyu.V4.SingletonV4@383a0ba
Thread-89,com.xuyu.V4.SingletonV4@383a0ba
Thread-92,com.xuyu.V4.SingletonV4@383a0ba
Thread-91,com.xuyu.V4.SingletonV4@383a0ba
....
Thread-8,com.xuyu.V4.SingletonV4@383a0ba
Thread-6,com.xuyu.V4.SingletonV4@383a0ba
Thread-9,com.xuyu.V4.SingletonV4@383a0ba
Thread-12,com.xuyu.V4.SingletonV4@383a0ba
Thread-11,com.xuyu.V4.SingletonV4@383a0ba
Thread-10,com.xuyu.V4.SingletonV4@383a0ba
Thread-15,com.xuyu.V4.SingletonV4@383a0ba
Thread-19,com.xuyu.V4.SingletonV4@383a0ba
Thread-16,com.xuyu.V4.SingletonV4@383a0ba
5.靜態內部內形式
package com.xuyu.V5;
/**
* author:須臾
*/
public class SingletonV5 {
private SingletonV5(){
System.out.println("對象初始化...");
}
public static SingletonV5 getInstance(){
return SingletonV5Utils.singletonV5;
}
/**
* 靜態內部類方式:能夠避免同步帶來的效率問題和實現懶加載
*/
public static class SingletonV5Utils{
private static SingletonV5 singletonV5=new SingletonV5();
}
/**
* 測試單例
*/
public static void main(String[] args) {
System.out.println("項目啓動成功。。。");
SingletonV5 instance1 = SingletonV5.getInstance();
SingletonV5 instance2 = SingletonV5.getInstance();
System.out.println(instance1==instance2);
}
}
輸出結果
項目啓動成功。。。
對象初始化...
true
6.枚舉形式
package com.xuyu.V6;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* author:須臾
*/
public enum EnumSingleton {
INSTANCE;
// 枚舉能夠絕對有效的防止實例化多次,和防止反射和序列化破壞
public void add() {
System.out.println("add方法...");
}
/**
* 測試單例
*/
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1==instance2);
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingleton v6 = declaredConstructor.newInstance();
System.out.println(v6==instance1);
}
}
輸出結果:反射破壞不了單例
true
Exception in thread "main" java.lang.NoSuchMethodException: com.xuyu.V6.EnumSingleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.xuyu.V6.EnumSingleton.main(EnumSingleton.java:19)
7.使用容器管理
package com.xuyu.V7;
import java.util.HashMap;
import java.util.Map;
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String, Object>();
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
{
return objMap.get(key);
}
}
}
這種使用SingletonManager 將多種單例類統一管理,在使用時根據key獲取對象對應類型的對象。這種方式使得我們可以管理多種類型的單例,並且在使用時可以通過統一的接口進行獲取操作,降低了用戶的使用成本,也對用戶隱藏了具體實現,降低了耦合度。
如何防止破壞單例
雖然單例通過私有構造函數,可以實現防止程序猿初始化對象,但是還可以通過反射和序列化技術破壞單例。
1.使用反射技術破壞單例
// 1. 使用懶漢式創建對象
SingletonV3 instance1 = SingletonV3.getInstance();
// 2. 使用Java反射技術初始化對象 執行無參構造函數
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
如何防止被反射破壞
私有構造函數
private SingletonV3() throws Exception {
synchronized (SingletonV3.class) {
if (singletonV3 != null) {
throw new Exception("該對象已經初始化..");
}
System.out.println("執行SingletonV3無參構造函數...");
}
}
2.使用序列化技術破壞單例
Singleton instance = Singleton.getInstance();
FileOutputStream fos = new FileOutputStream("E:\\code\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("E:\\code\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton singleton2 = (Singleton) ois.readObject();
System.out.println(singleton2==instance)
//返回序列化獲取對象 ,保證爲單例
public Object readResolve() {
return singletonV3;
}
枚舉單例爲什麼不能夠反射初始化
枚舉底層原理
1.首先如果使用java的反射機制破壞單例,報錯
通過該錯誤說明,該枚舉類中無參數構造函數
2.使用java反編譯技術,查看枚舉類
從該圖可以得出一個結論,枚舉底層其實類。
- 枚舉類底層原理分析
使用靜態代碼快方式,當靜態代碼快執行的時候初始化該對象,從而可以讓開發者使用通過EnumSingleton.INSTANCE使用。
- 在該反編譯源碼中,定義了一個類繼承了Enum 該類是中沒有無參構造函數,所以反射機制調用無參構造函數是無法初始化的。
- 在該類中有一個只有一個有參構造函數
調用父類構造構造函數
Name 爲定義調用對象名稱,定義調用對象序號ordinal
使用注入有參構造函數是否可以破壞枚舉呢?也不行的。
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingleton v3 = declaredConstructor.newInstance();
System.out.println(v3 == instance1);
爲什麼報這個錯誤呢?主要原因是 java的反射初始化對象中,只要對象是是枚舉是不會初始化的的。
總結
到這裏七中寫法都介紹完了,至於選擇用哪種形式的單例模式,取決於你的項目本身,是否是有複雜的併發環境,還是需要控制單例對象的資源消耗。
設計模式總結
策略模式:
官方描述(定義一系列算法,把他們封裝起來,並且使它們可以相互替換)
白話文描述:有共同的抽象行爲,具體不同的行爲稱作爲不同的策略,最終可以使用Context上下文獲取對應策略。
應用場景:解決多重if判斷問題、聚合支付平臺、第三方聯合登陸、調用多個不同短信接口等。
責任鏈模式:
官方描述:(將請求的發送者和接收者解耦,使的多個對象都有處理這個請求的機會。)
白話文描述:每一個業務模塊之間相互依賴比較有關聯、每個關聯模塊稱作爲handler(處理器)使用上一個handler引用到下一個hanlder實現一個鏈表。
應用場景: 權限控制、網關權限控制、審批、風控系統等。
模版方法:
官方描述:定義一個算法結構,而將一些步驟延遲到子類實現。
白話文描述:
提前定義好整體的骨架,不同的行爲讓子類實現,相同的行爲直接定義在抽象類中複用。
有大體共同抽象行爲全部交給父類實現,不同的行爲讓子類實現。
應用場景:支付異步回調重構、Servlet實現
裝飾模式:
官方描述:動態的給對象添加新的功能。
白話文描述:
在不改變原有對象的基礎上附加功能,相比生成子類更靈活。
應用場景:IO流
代理模式:
官方描述:爲其他對象提供一個代理以便控制這個對象的訪問。
白話文描述:
在方法之前和之後做一些處理 實現AOP通知
應用場景:AOP、事務、日誌、權限控制
觀察者模式:
官方描述: 對象間的一對多的依賴關係。
白話文描述:
在對象之間定義一對多的依賴,這樣一來,當一個對象改變狀態,依賴它的對象收到通知並自動更新
其實就是發佈訂閱模式,發佈者發佈消息,訂閱者獲取消息,訂閱了就能收到消息,沒訂閱就收不到消息。應用場景: 發佈訂閱 事件通知、 Zookeeper、事件監聽操作
門面模式:
官方描述: 對外提供一個統一的方法,來訪問子系統中的一羣接口。
該模式就是把一些複雜的流程封裝成一個接口供給外部用戶更簡單的使用
狀態模式:
官方描述: 允許一個對象在其對象內部狀態改變時改變它的行爲。
白話文 狀態模式與策略模式本質上沒有很大區別,主要根據行爲決定,如果有共同抽象行爲使用策略模式,沒有共同行爲就使用狀態模式。
適配器模式:
官方描述: 將一個類的方法接口轉換成客戶希望的另外一個接口。應用場景: mybatis日誌收集、提供接口轉換。
單例模式
官方描述:保證在一個jvm中只能有一個實例
本文出自螞蟻課堂:http://www.mayikt.com