您好,登錄后才能下訂單哦!
這篇文章主要介紹了java設(shè)計(jì)模式之怎么實(shí)現(xiàn)單例模式的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇java設(shè)計(jì)模式之怎么實(shí)現(xiàn)單例模式文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。
單元素的枚舉類型經(jīng)常成為實(shí)現(xiàn) Singleton 的最佳方法 。
什么是單例?就一條基本原則,單例對(duì)象的類只會(huì)被初始化一次。在 Java 中,我們可以說(shuō)在 JVM 中只存在該類的唯一一個(gè)對(duì)象實(shí)例。在 Android 中,我們可以說(shuō)在程序運(yùn)行期間,該類有且僅有一個(gè)對(duì)象實(shí)例。
單例模式的簡(jiǎn)單實(shí)現(xiàn)步驟:
構(gòu)造方法私有,保證無(wú)法從外部通過(guò) new 的方式創(chuàng)建對(duì)象。
對(duì)外提供獲取該類實(shí)例的靜態(tài)方法。
類的內(nèi)部創(chuàng)建該類的對(duì)象,通過(guò)第 2 步的靜態(tài)方法返回。
按照上述步驟寫下你認(rèn)為比較嚴(yán)謹(jǐn)?shù)膯卫J?,然后看看你所寫下的單例能否滿足以下條件:
你的單例按需加載嗎?
你的單例線程安全嗎?涉及到并發(fā)三要素:原子性、可見(jiàn)性、有序性
你的單例暴力反射和序列化安全嗎?
//JAVA實(shí)現(xiàn)public class SingleTon { //第三步創(chuàng)建唯一實(shí)例
private static SingleTon instance = new SingleTon();
//第一步構(gòu)造方法私有
private SingleTon() {
}
//第二步暴露靜態(tài)方法返回唯一實(shí)例
public static SingleTon getInstance() { return instance;
}
}//Kotlin實(shí)現(xiàn)object SingleTon
優(yōu)點(diǎn):設(shè)計(jì)簡(jiǎn)單 ,解決了多線程實(shí)例化的問(wèn)題。
缺點(diǎn):在虛擬機(jī)加載SingleTon類的時(shí)候,將會(huì)在初始化階段為類靜態(tài)變量賦值,也就是在虛擬機(jī)加載該類的時(shí)候(此時(shí)可能并沒(méi)有調(diào)用 getInstance 方法)就已經(jīng)調(diào)用了 new SingleTon();
創(chuàng)建了該對(duì)象的實(shí)例,之后不管這個(gè)實(shí)例對(duì)象用不用,都會(huì)占據(jù)內(nèi)存空間。
//JAVA實(shí)現(xiàn)public class SingleTon { //創(chuàng)建唯一實(shí)例
private static SingleTon instance = null;
private SingleTon() {
}
public static SingleTon getInstance() { //延遲初始化 在第一次調(diào)用 getInstance 的時(shí)候創(chuàng)建對(duì)象
if (instance == null) {
instance = new SingleTon();
} return instance;
}
}//Kotlin實(shí)現(xiàn)class SingleTon private constructor() { companion object { private var instance: SingleTon? = null
get() { if (field == null) {
field = SingleTon()
} return field
} fun get(): SingleTon{ return instance!!
}
}
}
優(yōu)點(diǎn):設(shè)計(jì)也是比較簡(jiǎn)單的,和餓漢式不同,當(dāng)這個(gè)Singleton被加載的時(shí)候,被static修飾的靜態(tài)變量將會(huì)被初始化為null,這個(gè)時(shí)候并不會(huì)占用內(nèi)存,而是當(dāng)?shù)谝淮握{(diào)用getInstance方法的時(shí)候才會(huì)被初始化實(shí)例對(duì)象,按需創(chuàng)建。
缺點(diǎn):在單線程環(huán)境下是沒(méi)有問(wèn)題的,在多線程環(huán)境下,會(huì)產(chǎn)生線程安全問(wèn)題。在有兩個(gè)線程同時(shí) 運(yùn)行到了 instane == null這個(gè)語(yǔ)句,并且都通過(guò)了,那他們就會(huì)都各自實(shí)例化一個(gè)對(duì)象,這樣就又不是單例了。
如何解決懶漢式在多線程環(huán)境下的多實(shí)例問(wèn)題?
靜態(tài)內(nèi)部類
//JAVA實(shí)現(xiàn)public class SingleTon {
private static class InnerSingleton{ private static SingleTon singleTon = new SingleTon();
} public SingleTon getInstance(){ return InnerSingleton.singleTon;
}
private SingleTon() {
}
}//kotlin實(shí)現(xiàn)class SingleTon private constructor() {
companion object { val instance = InnerSingleton.instance
} private object InnerSingleton { val instance = SingleTon()
}
}
直接同步方法
//JAVA實(shí)現(xiàn)public class SingleTon { //創(chuàng)建唯一實(shí)例
private static SingleTon instance = null;
private SingleTon() {
}
public static synchronized SingleTon getInstance() { if (instance == null) {
instance = new SingleTon();
} return instance;
}
}//Kotlin實(shí)現(xiàn)class SingleTon private constructor() { companion object { private var instance: SingleTon? = null
get() { if (field == null) {
field = SingleTon()
} return field
} @Synchronized
fun get(): SingleTon{ return instance!!
}
}
}
優(yōu)點(diǎn):加鎖只有一個(gè)線程能實(shí)例該對(duì)象,解決了線程安全問(wèn)題。
缺點(diǎn):對(duì)于靜態(tài)方法而言,synchronized關(guān)鍵字會(huì)鎖住整個(gè) Class,每次調(diào)用getInstance方法都會(huì)線程同步,效率十分低下,而且當(dāng)創(chuàng)建好實(shí)例對(duì)象之后,也就不必繼續(xù)進(jìn)行同步了。
備注:此處的synchronized保證了操作的原子性和內(nèi)存可見(jiàn)性。
同步代碼塊(雙重檢鎖方式DCL)
//JAVA實(shí)現(xiàn) public class SingleTon { //創(chuàng)建唯一實(shí)例
private static volatile SingleTon instance = null;
private SingleTon() {
}
public static SingleTon getInstance() { if (instance == null) {
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
} return instance;
}
}//kotlin實(shí)現(xiàn)class SingleTon private constructor() { companion object { val instance: SingleTon by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SingleTon()
}
}
}
或者class SingleTon private constructor() { companion object { @Volatile private var instance: SingleTon? = null
fun getInstance() =
instance ?: synchronized(this) {
instance ?: SingleTon().also { instance = it }
}
}
}
優(yōu)點(diǎn):添加了一個(gè)同步代碼塊,在同步代碼塊中去判斷實(shí)例對(duì)象是否存在,如果不存在則去創(chuàng)建,這個(gè)時(shí)候其實(shí)就完全可以解決問(wèn)題了,因?yàn)殡m然是多個(gè)線程去獲取實(shí)例對(duì)象,但是在同一個(gè)時(shí)間也只會(huì)有一個(gè)線程會(huì)進(jìn)入到同步代碼塊,那么這個(gè)時(shí)候創(chuàng)建好對(duì)象之后,其他線程即便再次進(jìn)入同步代碼塊,由于已經(jīng)創(chuàng)建好了實(shí)例對(duì)象,便直接返回即可。但是為什么還要在同步代碼塊的上一步再次去判斷instance為空呢?這個(gè)是由于當(dāng)我們創(chuàng)建好實(shí)例對(duì)象之后,直接去判斷此實(shí)例對(duì)象是否為空,如果不為空,則直接返回就好了,就避免再次進(jìn)去同步代碼塊了,提高了性能。
缺點(diǎn):無(wú)法避免暴力反射創(chuàng)建對(duì)象。
備注:此處的volatile發(fā)揮了內(nèi)存可見(jiàn)性及防止指令重排序作用。
public enum SingletonEnum { INSTANCE; public static void main(String[] args) { System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);
}
}
枚舉實(shí)現(xiàn)單例是最為推薦的一種方法,因?yàn)榫退阃ㄟ^(guò)序列化,反射等也沒(méi)辦法破壞單例性。(關(guān)于Android使用枚舉會(huì)產(chǎn)生性能問(wèn)題的說(shuō)法,這應(yīng)該是Android 2.x系統(tǒng)之前內(nèi)存緊張的時(shí)代了,現(xiàn)在已經(jīng)Android 13了,相信某些場(chǎng)合枚舉所帶來(lái)的便利遠(yuǎn)遠(yuǎn)大于這點(diǎn)所謂的性能影響)
以最初的DCL為測(cè)試案例,看看如何進(jìn)行反射攻擊及又如何在一定程度上避免反射攻擊。
反射攻擊代碼如下:
public static void main(String[] args) {
SingleTon singleton1 = SingleTon.getInstance();
SingleTon singleton2 = null;
try {
Class<SingleTon> clazz = SingleTon.class;
Constructor<SingleTon> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
singleton2 = constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("singleton1.hashCode():" + singleton1.hashCode());
System.out.println("singleton2.hashCode():" + singleton2.hashCode());
}
執(zhí)行結(jié)果:
singleton1.hashCode():1296064247
singleton2.hashCode():1637070917
通過(guò)執(zhí)行結(jié)果發(fā)現(xiàn)通過(guò)反射破壞了單例。 如何保證反射安全呢?只能以暴制暴,當(dāng)已經(jīng)存在實(shí)例的時(shí)候再去調(diào)用構(gòu)造函數(shù)直接拋出異常,對(duì)構(gòu)造函數(shù)做如下修改:
public class SingleTon { //創(chuàng)建唯一實(shí)例
private static volatile SingleTon instance = null;
private SingleTon() { if (instance != null) { throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");
}
}
public static SingleTon getInstance() { if (instance == null) {
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
} return instance;
}
}
此時(shí)可防御反射攻擊,拋出異常如下:
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.imock.demo.TestUtil.testSingleInstance(TestUtil.java:45)
at com.imock.demo.TestUtil.main(TestUtil.java:33)
Caused by: java.lang.RuntimeException: 單例構(gòu)造器禁止反射調(diào)用
at com.imock.demo.SingleTon.<init>(SingleTon.java:16)
... 6 more Exception in thread "main" java.lang.NullPointerException
at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:49)
at com.imock.demo.TestUtil.main(TestUtil.java:33)
Process finished with exit code 1
然后我們把上述測(cè)試代碼修改如下(調(diào)換了singleton1的初始化順序)
:
public static void main(String[] args) {
SingleTon singleton2 = null;
try {
Class<SingleTon> clazz = SingleTon.class;
Constructor<SingleTon> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
singleton2 = constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("singleton2.hashCode():" + singleton2.hashCode());
SingleTon singleton1 = SingleTon.getInstance(); //調(diào)換了位置,在反射之后執(zhí)行
System.out.println("singleton1.hashCode():" + singleton1.hashCode());
}
執(zhí)行結(jié)果:
singleton2.hashCode():1296064247
singleton1.hashCode():1637070917
發(fā)現(xiàn)此防御未起到作用。
缺點(diǎn):
如果反射攻擊發(fā)生在正常調(diào)用getInstance之前,每次反射攻擊都可以獲取單例類的一個(gè)實(shí)例,因?yàn)榧词顾接袠?gòu)造器中使用了靜態(tài)成員(instance) ,但單例對(duì)象并沒(méi)有在類的初始化階段被實(shí)例化,所以防御代碼不生效,從而可以通過(guò)構(gòu)造器的反射調(diào)用創(chuàng)建單例類的多個(gè)實(shí)例;
如果反射攻擊發(fā)生在正常調(diào)用之后,防御代碼是可以生效的;
如何避免序列化攻擊?只需要修改反序列化的邏輯就可以了,即重寫 readResolve()
方法,使其返回統(tǒng)一實(shí)例。
protected Object readResolve() { return getInstance();
}
脆弱不堪的單例模式經(jīng)過(guò)重重考驗(yàn),進(jìn)化成了完全體,延遲加載,線程安全,反射及序列化安全。簡(jiǎn)易代碼如下:
餓漢模式
public class SingleTon { private static SingleTon instance = new SingleTon();
private SingleTon() { if (instance != null) { throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");
}
} public static SingleTon getInstance() { return instance;
}
}
靜態(tài)內(nèi)部類
public class SingleTon {
private static class InnerStaticClass{ private static SingleTon singleTon = new SingleTon();
} public SingleTon getInstance(){ return InnerStaticClass.singleTon;
}
private SingleTon() { if (InnerStaticClass.singleTon != null) { throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");
}
}
}
懶漢模式
public class SingleTon { //創(chuàng)建唯一實(shí)例
private static SingleTon instance = null;
private SingleTon() { if (instance != null) { throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");
}
}
public static SingleTon getInstance() { //延遲初始化 在第一次調(diào)用 getInstance 的時(shí)候創(chuàng)建對(duì)象
if (instance == null) {
instance = new SingleTon();
} return instance;
}
}
缺點(diǎn):
如果反射攻擊發(fā)生在正常調(diào)用getInstance之前,每次反射攻擊都可以獲取單例類的一個(gè)實(shí)例,因?yàn)榧词顾接袠?gòu)造器中使用了靜態(tài)成員(instance) ,但單例對(duì)象并沒(méi)有在類的初始化階段被實(shí)例化,所以防御代碼不生效,從而可以通過(guò)構(gòu)造器的反射調(diào)用創(chuàng)建單例類的多個(gè)實(shí)例;
如果反射攻擊發(fā)生在正常調(diào)用之后,防御代碼是可以生效的。
(枚舉實(shí)現(xiàn)單例是最為推薦的一種方法,因?yàn)榫退阃ㄟ^(guò)序列化,反射等也沒(méi)辦法破壞單例性,底層實(shí)現(xiàn)比如newInstance方法內(nèi)部判斷枚舉拋異常)
Java主要應(yīng)用于:1. web開(kāi)發(fā);2. Android開(kāi)發(fā);3. 客戶端開(kāi)發(fā);4. 網(wǎng)頁(yè)開(kāi)發(fā);5. 企業(yè)級(jí)應(yīng)用開(kāi)發(fā);6. Java大數(shù)據(jù)開(kāi)發(fā);7.游戲開(kāi)發(fā)等。
關(guān)于“java設(shè)計(jì)模式之怎么實(shí)現(xiàn)單例模式”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“java設(shè)計(jì)模式之怎么實(shí)現(xiàn)單例模式”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。