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()中重寫(xiě)readResolve方法:
//重寫(xiě)這個(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的,可以不寫(xiě)
Singleton2() {
System.out.println("private Singleton2()");
}
//重寫(xiě)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-09
java自定義日志輸出文件(log4j日志文件輸出多個(gè)自定義日志文件)
打印日志的在程序中是必不可少的,如果需要將不同的日志打印到不同的地方,則需要定義不同的Appender,然后定義每一個(gè)Appender的日志級(jí)別、打印形式和日志的輸出路徑,下面看一個(gè)示例吧2014-01-01
Mybatis批量修改時(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-11
Java之獲取客戶端真實(shí)IP地址的實(shí)現(xiàn)
在開(kāi)發(fā)工作中,我們常常需要獲取客戶端的IP,本文主要介紹了Jav之獲取客戶端真實(shí)IP地址的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
Springboot 讀取自定義pro文件注入static靜態(tài)變量方式
這篇文章主要介紹了Springboot 讀取自定義pro文件注入static靜態(tài)變量方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java web網(wǎng)站訪問(wèn)量的統(tǒng)計(jì)
這篇文章主要為大家詳細(xì)介紹了Java web網(wǎng)站訪問(wèn)量的統(tǒng)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
Java?Stream?流中?Collectors.toMap?的用法詳解
這篇文章主要介紹了Stream?流中?Collectors.toMap?的用法,Collectors.toMap()方法是把List轉(zhuǎn)Map的操作,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01
Javas使用Redlock實(shí)現(xiàn)分布式鎖過(guò)程解析
這篇文章主要介紹了Javas使用Redlock實(shí)現(xiàn)分布式鎖過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08

