Java設(shè)計(jì)模式之單例模式示例詳解
0.概述
為什么要使用單例模式?
在我們的系統(tǒng)中,有一些對(duì)象其實(shí)我們只需要一個(gè),比如說(shuō):線程池、緩存、對(duì)話框、注冊(cè)表、日志對(duì)象、充當(dāng)打印機(jī)、顯卡等設(shè)備驅(qū)動(dòng)程序的對(duì)象。事實(shí)上,這一類對(duì)象只能有一個(gè)實(shí)例,如果制造出多個(gè)實(shí)例就可能會(huì)導(dǎo)致一些問(wèn)題的產(chǎn)生,比如:程序的行為異常、資源使用過(guò)量、或者不一致性的結(jié)果。因此這里需要用到單例模式
使用單例模式的好處?
對(duì)于頻繁使用的對(duì)象,可以省略創(chuàng)建對(duì)象所花費(fèi)的時(shí)間,這對(duì)于那些重量級(jí)對(duì)象而言,是非??捎^的一筆系統(tǒng)開(kāi)銷
由于new 操作的次數(shù)減少,因而對(duì)系統(tǒng)內(nèi)存的使用頻率也會(huì)降低,這將減輕 GC 壓力,縮短 GC 停頓時(shí)間
1.餓漢式
1.1 餓漢式單例實(shí)現(xiàn)
實(shí)例會(huì)提前創(chuàng)建:
/** * 餓漢式 * * @author xppll * @date 2021/12/24 21:21 */ public class Singleton1 implements Serializable { //構(gòu)造私有 private Singleton1() { System.out.println("private Singleton1()"); } //唯一實(shí)例 private static final Singleton1 INSTANCE = new Singleton1(); //獲得實(shí)例方法 public static Singleton1 getINSTANCE() { return INSTANCE; } //其他方法 public static void otherMethod() { System.out.println("otherMethod()"); } }
測(cè)試:
/** * @author xppll * @date 2021/12/24 21:28 */ public class TestSingleton { public static void main(String[] args) { //觸發(fā)Singleton1類的初始化,會(huì)為類的靜態(tài)變量賦予正確的初始值,單例對(duì)象就會(huì)被創(chuàng)建! Singleton1.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton1.getINSTANCE()); System.out.println(Singleton1.getINSTANCE()); } } //輸出: private Singleton1() otherMethod() ----------------------------------- singleton.Singleton1@10bedb4 singleton.Singleton1@10bedb4
1.2 破壞單例的幾種情況
1.反射破壞單例
2.反序列化破壞單例
3.Unsafe破壞單例
演示:
/** * @author xppll * @date 2021/12/24 21:28 */ public class TestSingleton { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException { //觸發(fā)Singleton1類的初始化,會(huì)為類的靜態(tài)變量賦予正確的初始值,單例對(duì)象就會(huì)被創(chuàng)建! Singleton1.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton1.getINSTANCE()); System.out.println(Singleton1.getINSTANCE()); //反射破壞單例 reflection(Singleton1.class); //反序列化破壞單例 serializable(Singleton1.getINSTANCE()); //Unsafe破壞單例 unsafe(Singleton1.class); } //反射破壞單例 private static void reflection(Class<?> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //得到無(wú)參 Constructor<?> constructor = clazz.getDeclaredConstructor(); //將此對(duì)象的 accessible 標(biāo)志設(shè)置為指示的布爾值,即設(shè)置其可訪問(wèn)性 constructor.setAccessible(true); //創(chuàng)建實(shí)例 System.out.println("反射創(chuàng)建實(shí)例:" + constructor.newInstance()); } //反序列化破壞單例 private static void serializable(Object instance) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); //序列化 oos.writeObject(instance); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); //反序列化 System.out.println("反序列化創(chuàng)建示例:" + ois.readObject()); } //Unsafe破壞單例 private static void unsafe(Class<?> clazz) throws InstantiationException { Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz); System.out.println("Unsafe 創(chuàng)建實(shí)例:" + o); } }
結(jié)果:
可以看出三種方式都會(huì)破壞單例!
1.3 預(yù)防單例的破壞
預(yù)防反射破壞單例
在構(gòu)造方法中加個(gè)判斷即可:
//構(gòu)造私有 private Singleton1() { //防止反射破壞單例 if(INSTANCE!=null){ throw new RuntimeException("單例對(duì)象不能重復(fù)創(chuàng)建"); } System.out.println("private Singleton1()"); }
預(yù)防反序列化破壞單例
在Singleton1()
中重寫readResolve
方法:
//重寫這個(gè)方法,如果序列化了,就會(huì)返回這個(gè),不會(huì)返回反序列化的對(duì)象 public Object readResolve(){ return INSTANCE; }
Unsafe破壞單例無(wú)法預(yù)防
2.枚舉餓漢式
2.1 枚舉單例實(shí)現(xiàn)
枚舉實(shí)現(xiàn)單例:
/** * 枚舉實(shí)現(xiàn)單例 * * @author xppll * @date 2021/12/24 22:23 */ public enum Singleton2 { INSTANCE; //枚舉的構(gòu)造方法默認(rèn)是private的,可以不寫 Singleton2() { System.out.println("private Singleton2()"); } //重寫toString方法 @Override public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } //獲得實(shí)例方法(這個(gè)可以不要,枚舉變量都是public的) public static Singleton2 getInstance() { return INSTANCE; } //其他方法 public static void otherMethod() { System.out.println("otherMethod()"); } }
測(cè)試:
/** * @author xppll * @date 2021/12/24 21:28 */ public class TestSingleton { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException { //觸發(fā)Singleton2類的初始化,會(huì)為類的靜態(tài)變量賦予正確的初始值,單例對(duì)象就會(huì)被創(chuàng)建! Singleton2.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton2.getInstance()); System.out.println(Singleton2.getInstance()); } } //輸出: private Singleton2() otherMethod() ----------------------------------- singleton.Singleton2@2de80c singleton.Singleton2@2de80c
可以看出當(dāng)調(diào)用otherMethod()
時(shí),就會(huì)觸發(fā)類的加載,枚舉對(duì)象就會(huì)創(chuàng)建,所以枚舉實(shí)現(xiàn)單例是餓漢式的
2.2 破壞單例
枚舉類實(shí)現(xiàn)單例的好處:
1.反序列化無(wú)法破壞枚舉單例
2.反射無(wú)法破壞枚舉單例
栗子:
需要先修改反射破壞代碼,枚舉需要有參構(gòu)造
public class TestSingleton { public static void main(String[] args) throws Exception { Singleton5.otherMethod(); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println(Singleton5.getInstance()); System.out.println(Singleton5.getInstance()); //反序列化破壞單例 serializable(Singleton2.getInstance()); //Unsafe破壞單例 unsafe(Singleton2.class); //反射破壞單例 reflection(Singleton2.class); } private static void unsafe(Class<?> clazz) throws InstantiationException { Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz); System.out.println("Unsafe 創(chuàng)建實(shí)例:" + o); } private static void serializable(Object instance) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(instance); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); System.out.println("反序列化創(chuàng)建實(shí)例:" + ois.readObject()); } private static void reflection(Class<?> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,int.class); constructor.setAccessible(true); System.out.println("反射創(chuàng)建實(shí)例:" + constructor.newInstance()); } }
結(jié)果:
可以看出
1.反射是無(wú)法創(chuàng)建枚舉對(duì)象!無(wú)法破壞枚舉單例
2.反序列化也不會(huì)破壞枚舉單例!
3.Unsafe依然會(huì)破壞!
3.懶漢式
實(shí)現(xiàn)代碼如下:
/** * 懶漢式 * * @author xppll * @date 2021/12/25 08:34 */ public class Singleton3 implements Serializable { //構(gòu)造私有 private Singleton3() { System.out.println("private Singleton3()"); } //唯一實(shí)例 private static Singleton3 INSTANCE = null; public static Singleton3 getInstance() { //第一次調(diào)用的時(shí)候才創(chuàng)建 if (INSTANCE == null) { INSTANCE = new Singleton3(); } return INSTANCE; } //其他方法 public static void otherMethod() { System.out.println("otherMethod()"); } }
測(cè)試:
/** * @author xppll * @date 2021/12/24 21:28 */ public class TestSingleton { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException { Singleton3.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton3.getInstance()); System.out.println(Singleton3.getInstance()); } }
結(jié)果:
可以看出只有在第一次調(diào)用getInstance()
時(shí)才會(huì)創(chuàng)建唯一的單例對(duì)象,因此是懶漢式的。
但是這種方式在多線程環(huán)境下是會(huì)有問(wèn)題的,可能多個(gè)線程會(huì)同時(shí)執(zhí)行INSTANCE = new Singleton3();
。因此這里需要在getInstance()
方法上加上synchronized
關(guān)鍵字保證多線程下的正確性:
public static synchronized Singleton3 getInstance() { //第一次調(diào)用的時(shí)候才創(chuàng)建 if (INSTANCE == null) { INSTANCE = new Singleton3(); } return INSTANCE; }
但是這種方法是有問(wèn)題的,第一次創(chuàng)建完對(duì)象后,以后的操作是不需要在加鎖的,所以這種方式會(huì)影響性能!
我們的目標(biāo)應(yīng)該是第一次創(chuàng)建單例的時(shí)候給予保護(hù),后續(xù)操作則不需要加鎖保護(hù)!
4.雙檢鎖懶漢式
針對(duì)上面的問(wèn)題,這里給出第四種方法雙檢鎖懶漢式進(jìn)行優(yōu)化:
/** * 雙檢鎖懶漢式 * * @author xppll * @date 2021/12/25 08:53 */ public class Singleton4 { //構(gòu)造私有 private Singleton4() { System.out.println("private Singleton4()"); } //唯一實(shí)例 //這里volatile的作用是保證共享變量有序性! private static volatile Singleton4 INSTANCE = null; //雙檢鎖優(yōu)化 public static synchronized Singleton4 getInstance() { //實(shí)例沒(méi)創(chuàng)建,才會(huì)進(jìn)入內(nèi)部的 synchronized 代碼塊,提高性能,防止每次都加鎖 if (INSTANCE == null) { //可能第一個(gè)線程在synchronized 代碼塊還沒(méi)創(chuàng)建完對(duì)象時(shí),第二個(gè)線程已經(jīng)到了這一步,所以里面還需要加上判斷 synchronized (Singleton4.class) { //也許有其他線程已經(jīng)創(chuàng)建實(shí)例,所以再判斷一次 if (INSTANCE == null) { INSTANCE = new Singleton4(); } } } return INSTANCE; } //其他方法 public static void otherMethod() { System.out.println("otherMethod()"); } }
5.內(nèi)部類懶漢式
內(nèi)部類懶漢式單例實(shí)現(xiàn):
/** * 內(nèi)部類懶漢式 * * @author xppll * @date 2021/12/25 09:24 */ public class Singleton5 { //構(gòu)造私有 private Singleton5() { System.out.println("private Singleton5()"); } //靜態(tài)內(nèi)部類實(shí)現(xiàn)懶漢式單例,靜態(tài)變量的創(chuàng)建會(huì)放在靜態(tài)代碼塊里執(zhí)行,jvm會(huì)保證其線程安全 //只有第一次用到內(nèi)部類時(shí),才會(huì)初始化創(chuàng)建單例 private static class Holder { static Singleton5 INSTANCE = new Singleton5(); } //獲得實(shí)例方法 public static Singleton5 getInstance() { return Holder.INSTANCE; } //其他方法 public static void otherMethod() { System.out.println("otherMethod()"); } }
測(cè)試:
/** * @author xppll * @date 2021/12/24 21:28 */ public class TestSingleton { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException { Singleton5.otherMethod(); System.out.println("-----------------------------------"); System.out.println(Singleton5.getInstance()); System.out.println(Singleton5.getInstance()); } }
結(jié)果:
可以看出內(nèi)部類實(shí)現(xiàn)單例也是懶漢式的!
6.JDK中單例的體現(xiàn)
Runtime 體現(xiàn)了餓漢式單例
System類下的Console 體現(xiàn)了雙檢鎖懶漢式單例
Collections 中的 EmptyNavigableSet內(nèi)部類懶漢式單例
Collections 中的ReverseComparator.REVERSE_ORDER 內(nèi)部類懶漢式單例
Comparators.NaturalOrderComparator.INSTANCE 枚舉餓漢式單例
到此這篇關(guān)于Java設(shè)計(jì)模式之單例模式示例詳解的文章就介紹到這了,更多相關(guān)Java單例模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)身份證號(hào)碼驗(yàn)證的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用java語(yǔ)言實(shí)現(xiàn)身份證號(hào)碼驗(yàn)證的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09java自定義日志輸出文件(log4j日志文件輸出多個(gè)自定義日志文件)
打印日志的在程序中是必不可少的,如果需要將不同的日志打印到不同的地方,則需要定義不同的Appender,然后定義每一個(gè)Appender的日志級(jí)別、打印形式和日志的輸出路徑,下面看一個(gè)示例吧2014-01-01Mybatis批量修改時(shí)出現(xiàn)報(bào)錯(cuò)問(wèn)題解決方案
這篇文章主要介紹了Mybatis批量修改時(shí)出現(xiàn)報(bào)錯(cuò)問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Java之獲取客戶端真實(shí)IP地址的實(shí)現(xiàn)
在開(kāi)發(fā)工作中,我們常常需要獲取客戶端的IP,本文主要介紹了Jav之獲取客戶端真實(shí)IP地址的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12Springboot 讀取自定義pro文件注入static靜態(tài)變量方式
這篇文章主要介紹了Springboot 讀取自定義pro文件注入static靜態(tài)變量方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java web網(wǎng)站訪問(wèn)量的統(tǒng)計(jì)
這篇文章主要為大家詳細(xì)介紹了Java web網(wǎng)站訪問(wèn)量的統(tǒng)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Java?Stream?流中?Collectors.toMap?的用法詳解
這篇文章主要介紹了Stream?流中?Collectors.toMap?的用法,Collectors.toMap()方法是把List轉(zhuǎn)Map的操作,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01Javas使用Redlock實(shí)現(xiàn)分布式鎖過(guò)程解析
這篇文章主要介紹了Javas使用Redlock實(shí)現(xiàn)分布式鎖過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08