單例模式中,你不知道的事~~

      網(wǎng)友投稿 686 2025-04-04

      單例模式可以說只要是一個合格的開發(fā)都會寫,但是如果要深究,小小的單例模式可以牽扯到很多東西,比如:多線程是否安全?是否懶加載?性能等等。還有你知道幾種單例模式的寫法呢?如何防止反射破壞單例模式?


      一、 單例模式

      1.1 定義

      單例模式就是在程序運(yùn)行中只實(shí)例化一次,創(chuàng)建一個全局唯一對象。有點(diǎn)像?Java?的靜態(tài)變量,但是單例模式要優(yōu)于靜態(tài)變量

      靜態(tài)變量在程序啟動的時候JVM就會進(jìn)行加載,如果不使用,會造成大量的資源浪費(fèi);

      單例模式能夠?qū)崿F(xiàn)懶加載,能夠在使用實(shí)例的時候才去創(chuàng)建實(shí)例。

      開發(fā)工具類庫中的很多工具類都應(yīng)用了單例模式,比例線程池、緩存、日志對象等,它們都只需要創(chuàng)建一個對象,如果創(chuàng)建多份實(shí)例,可能會帶來不可預(yù)知的問題,比如資源的浪費(fèi)、結(jié)果處理不一致等問題。

      1.2 單例的實(shí)現(xiàn)思路

      靜態(tài)化實(shí)例對象;

      私有化構(gòu)造方法,禁止通過構(gòu)造方法創(chuàng)建實(shí)例;

      提供一個公共的靜態(tài)方法,用來返回唯一實(shí)例。

      1.3 單例的好處

      只有一個對象,內(nèi)存開支少、性能好;

      避免對資源的多重占用;

      在系統(tǒng)設(shè)置全局訪問點(diǎn),優(yōu)化和共享資源訪問。

      二、 單例模式的實(shí)現(xiàn)

      餓漢模式

      懶漢模式

      雙重檢查鎖模式

      靜態(tài)內(nèi)部類單例模式

      枚舉類實(shí)現(xiàn)單例模式

      2.1 餓漢模式

      在定義靜態(tài)屬性時,直接實(shí)例化了對象

      public class HungryMode {

      /** * 利用靜態(tài)變量來存儲唯一實(shí)例 */ private static final HungryMode instance = new HungryMode(); /** * 私有化構(gòu)造函數(shù) */ private HungryMode(){ // 里面可以有很多操作 } /** * 提供公開獲取實(shí)例接口 * @return */ public static HungryMode getInstance(){ return instance; } }

      由于使用了static關(guān)鍵字,保證了在引用這個變量時,關(guān)于這個變量的所以寫入操作都完成,所以保證了JVM層面的線程安全

      不能實(shí)現(xiàn)懶加載,造成空間浪費(fèi):如果一個類比較大,我們在初始化的時就加載了這個類,但是我們長時間沒有使用這個類,這就導(dǎo)致了內(nèi)存空間的浪費(fèi)。

      所以,能不能只有用到?getInstance()方法,才會去初始化單例類,才會加載單例類中的數(shù)據(jù)。所以就有了:懶漢式。

      2.2 懶漢模式

      懶漢模式是一種偷懶的模式,在程序初始化時不會創(chuàng)建實(shí)例,只有在使用實(shí)例的時候才會創(chuàng)建實(shí)例,所以懶漢模式解決了餓漢模式帶來的空間浪費(fèi)問題。

      public class LazyMode {

      /** * 定義靜態(tài)變量時,未初始化實(shí)例 */ private static LazyMode instance; /** * 私有化構(gòu)造函數(shù) */ private LazyMode(){ // 里面可以有很多操作 } /** * 提供公開獲取實(shí)例接口 * @return */ public static LazyMode getInstance(){ // 使用時,先判斷實(shí)例是否為空,如果實(shí)例為空,則實(shí)例化對象 if (instance == null) { instance = new LazyMode(); } return instance; } }

      但是這種實(shí)現(xiàn)在多線程的情況下是不安全的,有可能會出現(xiàn)多份實(shí)例的情況:

      if (instance == null) {

      instance = new LazyMode();

      }

      假設(shè)有兩個線程同時進(jìn)入到上面這段代碼,因?yàn)闆]有任何資源保護(hù)措施,所以兩個線程可以同時判斷的?instance?都為空,都將去初始化實(shí)例,所以就會出現(xiàn)多份實(shí)例的情況。

      我們給getInstance()方法加上Synchronized關(guān)鍵字,使得getInstance()方法成為受保護(hù)的資源就能夠解決多份實(shí)例的問題。

      public class LazyModeSynchronized {

      /** * 定義靜態(tài)變量時,未初始化實(shí)例 */ private static LazyModeSynchronized instance; /** * 私有化構(gòu)造函數(shù) */ private LazyModeSynchronized(){ // 里面可以有很多操作 } /** * 提供公開獲取實(shí)例接口 * @return */ public synchronized static LazyModeSynchronized getInstance(){ /** * 添加class類鎖,影響了性能,加鎖之后將代碼進(jìn)行了串行化, * 我們的代碼塊絕大部分是讀操作,在讀操作的情況下,代碼線程是安全的 * */ if (instance == null) { instance = new LazyModeSynchronized(); } return instance; } }

      實(shí)現(xiàn)了懶加載,節(jié)約了內(nèi)存空間。

      在不加鎖的情況下,線程不安全,可能出現(xiàn)多份實(shí)例;

      在加鎖的情況下,會使程序串行化,使系統(tǒng)有嚴(yán)重的性能問題。

      懶漢模式中加鎖的問題,對于getInstance()方法來說,絕大部分的操作都是讀操作,讀操作是線程安全的,所以我們沒必讓每個線程必須持有鎖才能調(diào)用該方法,我們需要調(diào)整加鎖的問題。由此也產(chǎn)生了一種新的實(shí)現(xiàn)模式:雙重檢查鎖模式。

      2.3 雙重檢查鎖模式

      public class DoubleCheckLockMode {

      private static DoubleCheckLockMode instance; /** * 私有化構(gòu)造函數(shù) */ private DoubleCheckLockMode(){ } /** * 提供公開獲取實(shí)例接口 * @return */ public static DoubleCheckLockMode getInstance(){ // 第一次判斷,如果這里為空,不進(jìn)入搶鎖階段,直接返回實(shí)例 if (instance == null) { synchronized (DoubleCheckLockMode.class) { // 搶到鎖之后再次判斷是否為空 if (instance == null) { instance = new DoubleCheckLockMode(); } } } return instance; } }

      雙重檢查鎖模式解決了單例、性能、線程安全問題,但是這種寫法同樣存在問題:在多線程的情況下,可能會出現(xiàn)空指針問題,出現(xiàn)問題的原因是JVM在實(shí)例化對象的時候會進(jìn)行優(yōu)化和指令重排序操作。

      private SingletonObject(){

      // 第一步 int x = 10; // 第二步 int y = 30; // 第三步 Object o = new Object(); }

      上面的構(gòu)造函數(shù)SingletonObject(),JVM?會對它進(jìn)行指令重排序,所以執(zhí)行順序可能會亂掉,但是不管是那種執(zhí)行順序,JVM?最后都會保證所以實(shí)例都完成實(shí)例化。?如果構(gòu)造函數(shù)中操作比較多時,為了提升效率,JVM?會在構(gòu)造函數(shù)里面的屬性未全部完成實(shí)例化時,就返回對象。雙重檢測鎖出現(xiàn)空指針問題的原因就是出現(xiàn)在這里,當(dāng)某個線程獲取鎖進(jìn)行實(shí)例化時,其他線程就直接獲取實(shí)例使用,由于JVM指令重排序的原因,其他線程獲取的對象也許不是一個完整的對象,所以在使用實(shí)例的時候就會出現(xiàn)空指針異常問題。

      要解決雙重檢查鎖模式帶來空指針異常的問題,只需要使用volatile關(guān)鍵字,volatile關(guān)鍵字嚴(yán)格遵循h(huán)appens-before原則,即:在讀操作前,寫操作必須全部完成。

      public class DoubleCheckLockModelVolatile {

      /** * 添加volatile關(guān)鍵字,保證在讀操作前,寫操作必須全部完成 */ private static volatile DoubleCheckLockModelVolatile instance; /** * 私有化構(gòu)造函數(shù) */ private DoubleCheckLockModelVolatile(){ } /** * 提供公開獲取實(shí)例接口 * @return */ public static DoubleCheckLockModelVolatile getInstance(){ if (instance == null) { synchronized (DoubleCheckLockModelVolatile.class) { if (instance == null) { instance = new DoubleCheckLockModelVolatile(); } } } return instance; } }

      2.4 靜態(tài)內(nèi)部類模式

      靜態(tài)內(nèi)部類模式也稱單例持有者模式,實(shí)例由內(nèi)部類創(chuàng)建,由于?JVM?在加載外部類的過程中, 是不會加載靜態(tài)內(nèi)部類的, 只有內(nèi)部類的屬性/方法被調(diào)用時才會被加載, 并初始化其靜態(tài)屬性。靜態(tài)屬性由static修飾,保證只被實(shí)例化一次,并且嚴(yán)格保證實(shí)例化順序。

      public class StaticInnerClassMode {

      private StaticInnerClassMode(){ } /** * 單例持有者 */ private static class InstanceHolder{ private final static StaticInnerClassMode instance = new StaticInnerClassMode(); } /** * 提供公開獲取實(shí)例接口 * @return */ public static StaticInnerClassMode getInstance(){ // 調(diào)用內(nèi)部類屬性 return InstanceHolder.instance; } }

      這種方式跟餓漢式方式采用的機(jī)制類似,但又有不同。兩者都是采用了類裝載的機(jī)制來保證初始化實(shí)例時只有一個線程。不同的地方:

      餓漢式方式是只要Singleton類被裝載就會實(shí)例化,沒有Lazy-Loading的作用;

      靜態(tài)內(nèi)部類方式在Singleton類被裝載時并不會立即實(shí)例化,而是在需要實(shí)例化時,調(diào)用getInstance()方法,才會裝載SingletonInstance類,從而完成Singleton的實(shí)例化。

      類的靜態(tài)屬性只會在第一次加載類的時候初始化,所以在這里,JVM幫助我們保證了線程的安全性,在類進(jìn)行初始化時,別的線程是無法進(jìn)入的。

      所以這種方式在沒有加任何鎖的情況下,保證了多線程下的安全,并且沒有任何性能影響和空間的浪費(fèi)。

      2.5 枚舉類實(shí)現(xiàn)單例模式

      因?yàn)槊杜e類型是線程安全的,并且只會裝載一次,設(shè)計(jì)者充分的利用了枚舉的這個特性來實(shí)現(xiàn)單例模式,枚舉的寫法非常簡單,而且枚舉類型是所用單例實(shí)現(xiàn)中唯一一種不會被破壞的單例實(shí)現(xiàn)模式。

      public class EnumerationMode {

      private EnumerationMode(){ } /** * 枚舉類型是線程安全的,并且只會裝載一次 */ private enum Singleton{ INSTANCE; private final EnumerationMode instance; Singleton(){ instance = new EnumerationMode(); } private EnumerationMode getInstance(){ return instance; } } public static EnumerationMode getInstance(){ return Singleton.INSTANCE.getInstance(); } }

      需要頻繁的進(jìn)行創(chuàng)建和銷毀的對象;

      創(chuàng)建對象時耗時過多或耗費(fèi)資源過多,但又經(jīng)常用到的對象;

      工具類對象;

      頻繁訪問數(shù)據(jù)庫或文件的對象。

      三、單例模式的問題及解決辦法

      除枚舉方式外, 其他方法都會通過反射的方式破壞單例

      3.1 單例模式的破壞

      /**

      * 以靜態(tài)內(nèi)部類實(shí)現(xiàn)為例

      單例模式中,你不知道的事~~

      * @throws Exception

      */

      @Test

      public void singletonTest() throws Exception { Constructor constructor = StaticInnerClassMode.class.getDeclaredConstructor(); constructor.setAccessible(true); StaticInnerClassMode obj1 = StaticInnerClassMode.getInstance(); StaticInnerClassMode obj2 = StaticInnerClassMode.getInstance(); StaticInnerClassMode obj3 = (StaticInnerClassMode) constructor.newInstance(); System.out.println("輸出結(jié)果為:"+obj1.hashCode()+"," +obj2.hashCode()+","+obj3.hashCode()); }

      控制臺打印:

      輸出結(jié)果為:1454171136,1454171136,1195396074

      從輸出的結(jié)果我們就可以看出obj1和obj2為同一對象,obj3為新對象。obj3是我們通過反射機(jī)制,進(jìn)而調(diào)用了私有的構(gòu)造函數(shù),然后產(chǎn)生了一個新的對象。

      3.2 如何阻止單例破壞

      可以在構(gòu)造方法中進(jìn)行判斷,若已有實(shí)例, 則阻止生成新的實(shí)例,解決辦法如下:

      public class StaticInnerClassModeProtection {

      private static boolean flag = false; private StaticInnerClassModeProtection(){ synchronized(StaticInnerClassModeProtection.class){ if(flag == false){ flag = true; }else { throw new RuntimeException("實(shí)例已經(jīng)存在,請通過 getInstance()方法獲取!"); } } } /** * 單例持有者 */ private static class InstanceHolder{ private final static StaticInnerClassModeProtection instance = new StaticInnerClassModeProtection(); } /** * 提供公開獲取實(shí)例接口 * @return */ public static StaticInnerClassModeProtection getInstance(){ // 調(diào)用內(nèi)部類屬性 return InstanceHolder.instance; } }

      測試:

      /**

      * 在構(gòu)造方法中進(jìn)行判斷,若存在則拋出RuntimeException

      * @throws Exception

      */

      @Test

      public void destroyTest() throws Exception { Constructor constructor = StaticInnerClassModeProtection.class.getDeclaredConstructor(); constructor.setAccessible(true); StaticInnerClassModeProtection obj1 = StaticInnerClassModeProtection.getInstance(); StaticInnerClassModeProtection obj2 = StaticInnerClassModeProtection.getInstance(); StaticInnerClassModeProtection obj3 = (StaticInnerClassModeProtection) constructor.newInstance(); System.out.println("輸出結(jié)果為:"+obj1.hashCode()+"," +obj2.hashCode()+","+obj3.hashCode()); }

      控制臺打印:

      Caused by: java.lang.RuntimeException: 實(shí)例已經(jīng)存在,請通過 getInstance()方法獲取! at cn.van.singleton.demo.mode.StaticInnerClassModeProtection.(StaticInnerClassModeProtection.java:22) ... 35 more

      四、總結(jié)

      4.1 各種實(shí)現(xiàn)的對比

      Java 任務(wù)調(diào)度

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:智能制造 工業(yè)生產(chǎn)(制造業(yè) 人工智能)
      下一篇:怎樣將云文檔刪除(怎么能把云的內(nèi)容刪除干凈)
      相關(guān)文章
      亚洲爆乳AAA无码专区| 亚洲午夜无码久久久久小说 | 久久精品国产99精品国产亚洲性色| 亚洲无码黄色网址| 国产精品亚洲综合网站| 在线精品自拍亚洲第一区| 99亚洲乱人伦aⅴ精品| 亚洲色大18成人网站WWW在线播放 亚洲色大成WWW亚洲女子 | 亚洲国产精品无码久久| 亚洲中文字幕无码mv| 亚洲色偷偷色噜噜狠狠99网| 亚洲性无码AV中文字幕| 亚洲狠狠婷婷综合久久蜜芽| 亚洲av无码专区首页| 国产精品亚洲色图| 亚洲色偷偷综合亚洲AV伊人| 国产亚洲欧洲Aⅴ综合一区 | 国产亚洲AV手机在线观看| 久久精品国产亚洲7777| 国产成人麻豆亚洲综合无码精品 | 亚洲sss综合天堂久久久| 亚洲综合无码一区二区痴汉| 亚洲人成人无码.www石榴 | 亚洲第一视频网站| 亚洲精品第五页中文字幕 | 中文字幕亚洲一区| 久久精品国产亚洲沈樵| 亚洲av鲁丝一区二区三区| 久久久亚洲AV波多野结衣 | 亚洲熟妇av一区二区三区| 久久亚洲国产中v天仙www | 97亚洲熟妇自偷自拍另类图片| 亚洲男人电影天堂| 亚洲最大成人网色香蕉| 亚洲国产精品精华液| 亚洲国产精品激情在线观看| 亚洲熟妇无码另类久久久| 91亚洲精品视频| 99久久婷婷国产综合亚洲| 色噜噜的亚洲男人的天堂| 精品国产日韩亚洲一区|