Java單例模式的創(chuàng)建,破壞和防破壞詳解
前言
大家所熟知的單例模式只能創(chuàng)建唯一一個實(shí)例,今天我們介紹幾種常見的單例模式,同時說一說如何破壞單例模式,同時又怎么來防破壞。
單例模式
單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計(jì)模式之一。這種類型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。
這種模式涉及到一個單一的類,該類負(fù)責(zé)創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實(shí)例化該類的對象。
1、單例類只能有一個實(shí)例。
2、單例類必須自己創(chuàng)建自己的唯一實(shí)例。
3、單例類必須給所有其他對象提供這一實(shí)例。
單例模式的幾種實(shí)現(xiàn)方式
懶漢式,線程不安全
下面的懶漢式是線程不安全的,支持懶加載,因?yàn)闆]有加鎖 synchronized,所以嚴(yán)格意義上它并不算單例模式。
樣例代碼:
public class Singleton{ private static Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ return new Singleton(); } return instance; } }
懶漢式,線程安全
下面的這種方式可以保證線程安全,支持懶加載,優(yōu)點(diǎn)是第一次調(diào)用才初始化,避免內(nèi)存浪費(fèi)。缺點(diǎn)是必須加鎖synchronized 才能保證單例,但加鎖會影響效率。
樣例代碼:
public class Singleton{ private static Singleton instance; private Singleton(){ } public static synchronized Singleton getInstance(){ if(instance == null){ return new Singleton(); } return instance; } }
餓漢式
餓漢式,比較常用,但是容易參生垃圾對象,這種方式不支持懶加載,線程安全,優(yōu)點(diǎn)是沒有加鎖,執(zhí)行效率會提高。缺點(diǎn)是類加載時就初始化,浪費(fèi)內(nèi)存。
樣例代碼:
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){ } public static Singleton getInstance() { return instance; } }
雙檢鎖/雙重校驗(yàn)鎖
這種方式支持懶加載,線程安全,這種方式采用雙鎖機(jī)制,安全且在多線程情況下能保持高性能。
樣例代碼:
public class Singleton { private volatile static Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance==null){ synchronized (Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
登記式/靜態(tài)內(nèi)部類
這種方式支持懶加載,線程安全,這種方式能達(dá)到雙檢鎖方式一樣的功效,但實(shí)現(xiàn)更簡單。對靜態(tài)域使用延遲初始化,應(yīng)使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態(tài)域的情況,雙檢鎖方式可在實(shí)例域需要延遲初始化時使用。
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton(){ } public static final Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
枚舉
這種實(shí)現(xiàn)方式不支持懶加載,線程安全,不過還沒有被廣泛采用,但這是實(shí)現(xiàn)單例模式的最佳方法。它更簡潔,自動支持序列化機(jī)制,絕對防止多次實(shí)例化。這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機(jī)制,防止反序列化重新創(chuàng)建新的對象,絕對防止多次實(shí)例化。
public enum Singleton { INSTANCE; public void whateverMethod() { } }
模擬一個數(shù)據(jù)庫連接類:
public enum SingletonEnum { INSTANCE; private DBConnection connection = null; SingletonEnum(){ connection = new DBConnection(); } public DBConnection getConnection(){ return connection; } }
public class DBConnection{ }
public class TestConnection { public static void main(String[] args) { DBConnection con1 = DataSourceEnum.DATASOURCE.getConnection(); DBConnection con2 = DataSourceEnum.DATASOURCE.getConnection(); System.out.println(con1 == con2); //輸出結(jié)果為true。 } }
破壞單例模式
破壞單例模式主要有兩種方法:反射、反序列化
我們就拿最經(jīng)典的餓漢式來演示破壞和防破壞。
未破壞的情況
Singleton:
/** * Keafmd * * @ClassName: Singleton * @Description: 單例模式 * @author: 牛哄哄的柯南 * @date: 2021-09-07 10:53 */ public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){ } public static Singleton getInstance() { return instance; } }
測試類(未破壞):
/** * Keafmd * * @ClassName: SigletonTest * @Description: 測試類 * @author: 牛哄哄的柯南 * @date: 2021-09-07 11:04 */ public class SingletonTest { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1); //com.keafmd.Study.designPatterns.Blog.Singleton@610455d6 System.out.println(instance2); //com.keafmd.Study.designPatterns.Blog.Singleton@610455d6 System.out.println(instance1==instance2); //true } }
破壞后的情況
Singleton:(不改變)
/** * Keafmd * * @ClassName: Singleton * @Description: 單例模式 * @author: 牛哄哄的柯南 * @date: 2021-09-07 10:53 */ public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){ } public static Singleton getInstance() { return instance; } }
測試類(通過反射破壞):
package com.keafmd.Study.designPatterns.Blog; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * Keafmd * * @ClassName: SigletonTest * @Description: 測試類 * @author: 牛哄哄的柯南 * @date: 2021-09-07 11:04 */ public class SingletonTest { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1); //com.keafmd.Study.designPatterns.Blog.Singleton@610455d6 System.out.println(instance2); //com.keafmd.Study.designPatterns.Blog.Singleton@610455d6 System.out.println(instance1==instance2); //true //=====================破壞單例模式=================== //通過反射獲取實(shí)例,破壞單例 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton instance11 = constructor.newInstance(); Singleton instance22 = constructor.newInstance(); System.out.println(instance11); //com.keafmd.Study.designPatterns.Blog.Singleton@511d50c0 System.out.println(instance22); //com.keafmd.Study.designPatterns.Blog.Singleton@60e53b93 System.out.println(instance11==instance22); //false 證明單例模式已經(jīng)被破壞 } }
輸出結(jié)果:
com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
true
com.keafmd.Study.designPatterns.Blog.Singleton@511d50c0
com.keafmd.Study.designPatterns.Blog.Singleton@60e53b93
falseProcess finished with exit code 0
這種破壞是通過java的反射機(jī)制,創(chuàng)建一個實(shí)例,這種破壞方法通過setAccessible(true)的方法是java跳過檢測語法,可以臨時改變訪問權(quán)限,就可以獲取私有成員變量。
單例模式的防破壞
其實(shí)防止破壞最簡單的一種方式就是判斷下有沒有創(chuàng)建過實(shí)例,如果是第二次創(chuàng)建實(shí)例對象的時候,直接拋出異常,阻止創(chuàng)建即可。
重寫Singleton類:
package com.keafmd.Study.designPatterns.Blog; /** * Keafmd * * @ClassName: Singleton * @Description: 單例模式 * @author: 牛哄哄的柯南 * @date: 2021-09-07 10:53 */ public class Singleton { //阻止實(shí)例化 private static boolean flag=true; private static Singleton instance = new Singleton(); private Singleton (){ if(!flag){ throw new RuntimeException("這個單例模式類不能創(chuàng)建更多的對象了"); } } public static Singleton getInstance() { if(flag){ flag=false; //第一次創(chuàng)建時就會改變flag的值,導(dǎo)致后面創(chuàng)建不成功 } return instance; } }
測試類(未改變):
package com.keafmd.Study.designPatterns.Blog; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * Keafmd * * @ClassName: SigletonTest * @Description: 測試類 * @author: 牛哄哄的柯南 * @date: 2021-09-07 11:04 */ public class SingletonTest { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1); System.out.println(instance2); System.out.println(instance1==instance2); //=====================破壞單例模式=================== //通過反射獲取實(shí)例,破壞單例 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton instance11 = constructor.newInstance(); Singleton instance22 = constructor.newInstance(); System.out.println(instance11); System.out.println(instance22); System.out.println(instance11==instance22); } }
輸出結(jié)果:
com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
true
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.keafmd.Study.designPatterns.Blog.SingletonTest.main(SingletonTest.java:28)
Caused by: java.lang.RuntimeException: 這個單例模式類不能創(chuàng)建更多的對象了
at com.keafmd.Study.designPatterns.Blog.Singleton.<init>(Singleton.java:28)
... 5 moreProcess finished with exit code 1
這樣在執(zhí)行到Singleton instance22 = constructor.newInstance();這行的時候就會拋出異常,這樣就防止了破壞。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
springboot?全局異常處理和統(tǒng)一響應(yīng)對象的處理方式
這篇文章主要介紹了springboot?全局異常處理和統(tǒng)一響應(yīng)對象,主要包括SpringBoot 默認(rèn)的異常處理機(jī)制和SpringBoot 全局異常處理,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06Maven profile實(shí)現(xiàn)不同環(huán)境的配置管理實(shí)踐
這篇文章主要介紹了Maven profile實(shí)現(xiàn)不同環(huán)境的配置管理實(shí)踐,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09Spring Boot使用FastJson解析JSON數(shù)據(jù)的方法
本篇文章主要介紹了Spring Boot使用FastJson解析JSON數(shù)據(jù)的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02