<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java枚舉單例模式比DCL和靜態(tài)單例要好?

          共 11528字,需瀏覽 24分鐘

           ·

          2022-05-18 16:38

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來,我們一起精進!你不來,我和你的競爭對手一起精進!

          編輯:業(yè)余草

          liuchenyang0515.blog.csdn.net

          推薦:https://www.xttblog.com/?p=5340

          ?

          餓漢式懶漢式單例就不說了,DCL和靜態(tài)單例簡單介紹下,為后面講解枚舉單例作鋪墊。分析不易,歡迎一鍵三連~

          ?

          文章目錄

          • 雙重校驗鎖單例
          • 為什么要double check?去掉第二次check行不行
          • singleton為什么要加上volatile關鍵字
          • 靜態(tài)內(nèi)部類單例
          • 枚舉單例 (推薦??!)
          • 枚舉單例模式的使用
          • 反編譯分析單例枚舉類

          1. 雙重校驗鎖單例(DCL)

          public?class?Singleton?{

          ????private?static?volatile?Singleton?singleton;

          ????private?Singleton(){
          ????}
          ????
          ????public?static?Singleton?getInstance(){
          ????????if?(singleton?==?null){
          ????????????synchronized?(Singleton.class){
          ????????????????if?(singleton?==?null){
          ????????????????????singleton?=?new?Singleton();
          ????????????????}
          ????????????}
          ????????}
          ????????return?singleton;
          ????}
          }

          這種DCL寫法的優(yōu)點:不僅線程安全,而且延遲加載。

          1.1 為什么要double check?去掉第二次check行不行?

          當然不行,當2個線程同時執(zhí)行getInstance方法時,都會執(zhí)行第一個if判斷,由于鎖機制的存在,會有一個線程先進入同步語句,而另一個線程等待,當?shù)谝粋€線程執(zhí)行了new Singleton()之后,就會退出synchronized的保護區(qū)域,這時如果沒有第二重if判斷,那么第二個線程也會創(chuàng)建一個實例,這就破壞了單例。

          1.2 singleton為什么要加上volatile關鍵字?

          主要原因就是 singleton = new Singleton();不是一個原子操作。

          JVM中,這句語句至少做了3件事

          • 1.給Singleton的實例分配內(nèi)存空間;
          • 2.調(diào)用Singleton()的構造函數(shù),初始化成員字段;
          • 3.將singleton指向分配的內(nèi)存空間(此時singleton就不是null了)

          因為存在著指令重排序的優(yōu)化,第2、3步的順序是不能保證的,最后的執(zhí)行順序可能是1-2-3,也可能是1-3-2,假如執(zhí)行順序是1-3-2,我們看看會出現(xiàn)什么問題。

          雖然singleton不是null,但是指向的空間并沒有初始化,還是會報錯,這就是DCL失效的問題,這種問題難以跟蹤難以重現(xiàn)可能會隱藏很久。

          JDK1.5之前JMM(Java Memory Model,即Java內(nèi)存模型)中的Cache、寄存器到主存的回寫規(guī)定,上面第二第三的順序無法保證。JDK1.5之后,SUN官方調(diào)整了JVM,具體化了volatile關鍵字,private static volatile Singleton singleton;只要加上volatile,就可以保證每次從主存中讀?。ㄟ@涉及到CPU緩存一致性問題,不在本文探討范圍內(nèi),有興趣自行搜索),也可以防止指令重排序的發(fā)生,避免拿到未完成初始化的對象。

          簡單講,**volatile主要就是限制JIT編譯器優(yōu)化**,編譯器優(yōu)化常用的方法有:

          1. 將內(nèi)存變量緩存到寄存器;
          2. 調(diào)整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。

          如果沒有volatile關鍵字,則編譯器可能優(yōu)化讀取,使用寄存器中的緩存值,如果這個變量由別的線程更新了的話,將出現(xiàn)實際值和讀取的值不一致。「使用了volatile后,編譯器讀取的時候跳過緩存,直接在內(nèi)存中的實際位置讀變量,寫的時候通知其他緩存更新,這就是所謂的保證內(nèi)存可見性,并且使用volatile還能禁止指令重排序?!?/strong>

          public?volatile?int?a?=?11;
          ......
          int?c?=?6;
          c?=?a;//?執(zhí)行這一句的時候,在高并發(fā)情況下,a如果被修改為22,那么c會被賦值為22而不是11
          //如果a不被volatile修飾,c有小概率被賦值為11,因為c取寄存器的緩存副本11還沒來得及更新

          2. 靜態(tài)內(nèi)部類單例

          public?class?Singleton{
          ?private?Singleton(){}
          ?
          ?private?static?class?SingletonInstance?{
          ?????private?static?Singleton?singleton?=?new?Singleton();
          ?}
          ?
          ?public?static?Singleton?getInstance(){
          ???return?SingletonInstance.singleton;
          ?}
          }

          與餓漢式的區(qū)別就在于,類加載的時候,這里并不會實例化對象,只有調(diào)用getInstance方法才會實例化對象。
          DCL優(yōu)點一樣,延遲加載,效率高。

          雖然DCL和靜態(tài)單例都不錯,但是它們并不能防止反序列化和反射生成多個實例。更好的寫法當然是枚舉單例了!

          3. 枚舉單例 (推薦??!)

          其他所有的實現(xiàn)單例的方式其實是有問題的,那就是可能被反序列化和反射破壞。

          我們來看看JDK1.5中添加的枚舉類來實現(xiàn)單例

          public?enum?Singleton?{
          ?INSTANCE,

          ?public?void?testMethod()?{
          ??
          ?}
          }

          枚舉的寫法的優(yōu)點:

          • 1.不用考慮懶加載和線程安全的問題,代碼寫法簡潔優(yōu)雅

          • 2.線程安全

          反編譯任何一個枚舉類會發(fā)現(xiàn),枚舉類里的各個枚舉項是是通過static代碼塊來定義和初始化的(可以見后面3.2節(jié)反編譯分析單例枚舉有分析到這個),它們會在類被加載時完成初始化,而java類的加載由JVM保證線程安全,所以,創(chuàng)建一個Enum類型的枚舉是線程安全的

          • 防止破壞單例

          我們知道,序列化可以將一個單例的實例對象寫到磁盤,然后再反序列化讀回來,從而獲得一個新的實例。即使構造函數(shù)是私有的,反序列化時依然可以通過特殊的途徑去創(chuàng)建類的一個新的實例,相當于調(diào)用該類的構造函數(shù)。

          Java對枚舉的序列化作了規(guī)定,在序列化時,僅將枚舉對象的name屬性輸出到結果中,在反序列化時,就是通過java.lang.EnumvalueOf來根據(jù)名字查找對象,而不是新建一個新的對象。「枚舉在序列化和反序列化時,并不會調(diào)用構造方法」,這就防止了反序列化導致的單例破壞的問題。

          對于反射破壞單例的而言,枚舉類有同樣的防御措施,反射在通過newInstance創(chuàng)建對象時,會檢查這個類是否是枚舉類,如果是,會拋出異常java.lang.IllegalArgumentException: Cannot reflectively create enum objects,表示反射創(chuàng)建對象失敗。

          綜上,枚舉可以防止反序列化和反射破壞單例。

          3.1 枚舉單例模式的使用

          //?Singleton.java
          public?enum?Singleton?{
          ????INSTANCE;

          ????public?void?testMethod()?{
          ????????System.out.println("執(zhí)行了單例類的方法");
          ????}
          }

          //?Test.java
          public?class?Test?{
          ?public?static?void?main(String[]?args)?{
          ????????//演示如何使用枚舉寫法的單例類
          ????????Singleton.INSTANCE.testMethod();
          ????????System.out.println(Singleton.INSTANCE);
          ????}
          }

          運行結果如下:

          3.2 反編譯分析單例枚舉類

          為了讓大家進一步了解枚舉類,我們將上面枚舉單例類進行反編譯javap -p Singleton.class,其中-p的意思是反編譯的時候要包含私有方法。

          //?這是反編譯后的內(nèi)容
          public?final?class?Singleton?extends?java.lang.Enum<Singleton>?{
          ??public?static?final?Singleton?INSTANCE;
          ??private?static?final?Singleton[]?$VALUES;
          ??public?static?Singleton[]?values();
          ??public?static?Singleton?valueOf(java.lang.String);
          ??private?Singleton();
          ??public?void?testMethod();
          ??static?{};
          }

          我們可以看到,

          • INSTANCESingleton類的實例
          • Singleton繼承了java.lang.Enum
          • 這里還有一個私有的Singleton的無參構造方法,枚舉類的枚舉項都會使用這個構造方法來實例化,也就是說,這里的INSTANCE會使用這個構造方法來實例化。
          • 實例化的過程發(fā)生在最后空的static代碼塊中,可以通過javap的其他參數(shù)進一步分析static里面的字節(jié)碼內(nèi)容,static里面其實包含了很多字節(jié)碼指令,這些指令在做枚舉項INSTANCE的初始化工作,而static代碼塊是在類加載的時候執(zhí)行的,也就是說Singleton類被加載的時候,INSTANCE就被初始化了。static代碼塊里面除了初始化INSTANCE,Singleton[] $VALUES這個定義的私有的數(shù)組也是在static里面創(chuàng)建和初始化的。然后把所有枚舉項按照定義的順序放入這個$VALUES數(shù)組中,最后我們可以通過values方法來訪問這個數(shù)組

          為了分析每個方法中的操作,我們使用javap -p -c -v Singleton.class來看看更詳細的,-c來看每個方法中的字節(jié)碼,-v把常量池信息也打印出來,這里了解即可,看不懂就看我上面的結論吧,重點只需要看static代碼塊部分的字節(jié)碼,下面是為結論做一個驗證。

          /**
          ?*?@author:?磚業(yè)洋__
          ?*?@description:?我重點只分析最后的static部分
          ?*/

          public?final?class?Singleton?extends?java.lang.Enum<Singleton>
          ??minor?version:?0
          ??major?version:?52
          ??flags:?ACC_PUBLIC,?ACC_FINAL,?ACC_SUPER,?ACC_ENUM
          Constant?pool:?????//?需要注意常量池的部分,后面分析每條指令的時候可以回到這里查閱
          ???#1?
          =?Fieldref???????????#4.#37?????????//?Singleton.$VALUES:[LSingleton;
          ???#2?=?Methodref??????????#38.#39????????//?"[LSingleton;".clone:()Ljava/lang/Object;
          ???#3?=?Class??????????????#17????????????//?"[LSingleton;"
          ???#4?=?Class??????????????#40????????????//?Singleton
          ???#5?=?Methodref??????????#13.#41????????//?java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
          ???#6?=?Methodref??????????#13.#42????????//?java/lang/Enum."":(Ljava/lang/String;I)V
          ???#7?=?Fieldref???????????#43.#44????????//?java/lang/System.out:Ljava/io/PrintStream;
          ???#8?=?String?????????????#45????????????//?執(zhí)行了單例類的方法
          ???#9?=?Methodref??????????#46.#47????????//?java/io/PrintStream.println:(Ljava/lang/String;)V
          ??#10?=?String?????????????#14????????????//?INSTANCE
          ??#11?=?Methodref??????????#4.#42?????????//?Singleton."":(Ljava/lang/String;I)V
          ??#12?=?Fieldref???????????#4.#48?????????//?Singleton.INSTANCE:LSingleton;
          ??#13?=?Class??????????????#49????????????//?java/lang/Enum
          ??#14?=?Utf8???????????????INSTANCE
          ??#15?=?Utf8???????????????LSingleton;
          ??#16?=?Utf8???????????????$VALUES
          ??#17?=?Utf8???????????????[LSingleton;
          ??#18?=?Utf8???????????????values
          ??#19?=?Utf8???????????????()[LSingleton;
          ??#20?=?Utf8???????????????Code
          ??#21?=?Utf8???????????????LineNumberTable
          ??#22?=?Utf8???????????????valueOf
          ??#23?=?Utf8???????????????(Ljava/lang/String;)LSingleton;
          ??#24?=?Utf8???????????????LocalVariableTable
          ??#25?=?Utf8???????????????name
          ??#26?=?Utf8???????????????Ljava/lang/String;
          ??#27?=?Utf8???????????????
          ??#28?=?Utf8???????????????(Ljava/lang/String;I)V
          ??#29?=?Utf8???????????????this
          ??#30?=?Utf8???????????????Signature
          ??#31?=?Utf8???????????????()V
          ??#32?=?Utf8???????????????testMethod
          ??#33?=?Utf8???????????????
          ??#34?=?Utf8???????????????Ljava/lang/Enum;
          ??#35?=?Utf8???????????????SourceFile
          ??#36?=?Utf8???????????????Singleton.java
          ??#37?=?NameAndType????????#16:#17????????//?$VALUES:[LSingleton;
          ??#38?=?Class??????????????#17????????????//?"[LSingleton;"
          ??#39?=?NameAndType????????#50:#51????????//?clone:()Ljava/lang/Object;
          ??#40?=?Utf8???????????????Singleton
          ??#41?=?NameAndType????????#22:#52????????//?valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
          ??#42?=?NameAndType????????#27:#28????????//?"":(Ljava/lang/String;I)V
          ??#43?=?Class??????????????#53????????????//?java/lang/System
          ??#44?=?NameAndType????????#54:#55????????//?out:Ljava/io/PrintStream;
          ??#45?=?Utf8???????????????執(zhí)行了單例類的方法
          ??#46?=?Class??????????????#56????????????//?java/io/PrintStream
          ??#47?=?NameAndType????????#57:#58????????//?println:(Ljava/lang/String;)V
          ??#48?=?NameAndType????????#14:#15????????//?INSTANCE:LSingleton;
          ??#49?=?Utf8???????????????java/lang/Enum
          ??#50?=?Utf8???????????????clone
          ??#51?=?Utf8???????????????()Ljava/lang/Object;
          ??#52?=?Utf8???????????????(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
          ??#53?=?Utf8???????????????java/lang/System
          ??#54?=?Utf8???????????????out
          ??#55?=?Utf8???????????????Ljava/io/PrintStream;
          ??#56?=?Utf8???????????????java/io/PrintStream
          ??#57?=?Utf8???????????????println
          ??#58?=?Utf8???????????????(Ljava/lang/String;)V
          {
          ??public?static?final?Singleton?INSTANCE;?//?定義枚舉項
          ????descriptor:?LSingleton;
          ????flags:?ACC_PUBLIC,?ACC_STATIC,?ACC_FINAL,?ACC_ENUM

          ??private?static?final?Singleton[]?$VALUES;?//?定義對象數(shù)組,并沒有初始化,只是空引用
          ????descriptor:?[LSingleton;
          ????flags:?ACC_PRIVATE,?ACC_STATIC,?ACC_FINAL,?ACC_SYNTHETIC

          ??public?static?Singleton[]?values();
          ????descriptor:?()[LSingleton;
          ????flags:?ACC_PUBLIC,?ACC_STATIC
          ????Code:
          ??????stack=1,?locals=0,?args_size=0
          ?????????0:?getstatic?????#1??????????????????//?Field?$VALUES:[LSingleton;
          ?????????3:?invokevirtual?#2??????????????????//?Method?"[LSingleton;".clone:()Ljava/lang/Object;
          ?????????6:?checkcast?????#3??????????????????//?class?"[LSingleton;"
          ?????????9:?areturn
          ??????LineNumberTable:
          ????????line?1:?0

          ??public?static?Singleton?valueOf(java.lang.String);
          ????descriptor:?(Ljava/lang/String;)LSingleton;
          ????flags:?ACC_PUBLIC,?ACC_STATIC
          ????Code:
          ??????stack=2,?locals=1,?args_size=1
          ?????????0:?ldc???????????#4??????????????????//?class?Singleton
          ?????????2:?aload_0
          ?????????3:?invokestatic??#5??????????????????//?Method?java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
          ?????????6:?checkcast?????#4??????????????????//?class?Singleton
          ?????????9:?areturn
          ??????LineNumberTable:
          ????????line?1:?0
          ??????LocalVariableTable:
          ????????Start??Length??Slot??Name???Signature
          ????????????0??????10?????0??name???Ljava/lang/String;

          ??private?Singleton();
          ????descriptor:?(Ljava/lang/String;I)V
          ????flags:?ACC_PRIVATE
          ????Code:
          ??????stack=3,?locals=3,?args_size=3
          ?????????0:?aload_0???????//?棧操作指令,把局部方法表里的第0個位置的變量load加載到棧上來,a前綴表示它是一個引用類型。
          ???????//?提醒:?當JVM執(zhí)行一段代碼的時候,首先會把用到的所有的變量存在一個本地變量表里————局部變量表。
          ???????//?在棧上做計算的時候,需要使用局部方法表的值,就會通過load指令把它們加載到棧上來
          ???????//?在棧上運算完之后,需要把值存回到局部方法表,所以也會有對應的store指令,load和store是對應的。
          ?????????1:?aload_1
          ?????????2:?iload_2
          ?????????3:?invokespecial?#6??????????????????//?Method?java/lang/Enum."":(Ljava/lang/String;I)V
          ?????????6:?return
          ??????LineNumberTable:
          ????????line?1:?0
          ??????LocalVariableTable:
          ????????Start??Length??Slot??Name???Signature
          ????????????0???????7?????0??this???LSingleton;
          ????Signature:?#31??????????????????????????//?()V

          ??public?void?testMethod();
          ????descriptor:?()V
          ????flags:?ACC_PUBLIC
          ????Code:
          ??????stack=2,?locals=1,?args_size=1
          ?????????0:?getstatic?????#7??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
          ?????????3:?ldc???????????#8??????????????????//?String?執(zhí)行了單例類的方法
          ?????????5:?invokevirtual?#9??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/String;)V
          ?????????8:?return
          ??????LineNumberTable:
          ????????line?5:?0
          ????????line?6:?8
          ??????LocalVariableTable:
          ????????Start??Length??Slot??Name???Signature
          ????????????0???????9?????0??this???LSingleton;

          ??static?{};
          ????descriptor:?()V.????????//?就是代表返回void類型
          ????flags:?ACC_STATIC
          ????Code:
          ??????stack=4,?locals=0,?args_size=0
          ?????????0:?new???????????#4??????????????????//?class?Singleton
          ?????????//?new?#4表示從常量池里拿到標號4這個類型的名字,往上看Constant?pool部分的定義可知就是Singleton這個類,然后new出來變成對象
          ?????????3:?dup??????????//?然后dup壓棧
          ?????????4:?ldc???????????#10?????????????????//?String?INSTANCE,將常量池中標號10的String類型的值INSTANCE推送到棧頂
          ?????????6:?iconst_0????????//?定義一個int類型的變量值為0,我也不知道這里定義個常量有什么卵用
          ?????????7:?invokespecial?#11?????????????????//?Method?"":(Ljava/lang/String;I)V,調(diào)用構造器初始化,返回類型為void
          ????????10:?putstatic?????#12?????????????????//?Field?INSTANCE:LSingleton;給INSTANCE這個靜態(tài)變量賦值,和name一樣
          ????????13:?iconst_1????????//?定義一個int類型的變量值為1,然并卵
          ????????14:?anewarray?????#4??????????????????//?class?Singleton,實例化一個裝Singleton枚舉類型的數(shù)組,這里就是$VALUES數(shù)組
          ????????17:?dup
          ????????18:?iconst_0
          ????????19:?getstatic?????#12?????????????????//?Field?INSTANCE:LSingleton;取出字段INSTANCE的name值
          ????????22:?aastore?????????
          ????????23:?putstatic?????#1??????????????????//?Field?$VALUES:[LSingleton;將局部變量表中的枚舉項的name值都依次放入$VALUES數(shù)組中
          ????????26:?return
          ??????LineNumberTable:
          ????????line?2:?0
          ????????line?1:?13
          }

          其實編譯完的字節(jié)碼是給JVM看的,JVM只需要無腦順序往下執(zhí)行即可。多的方面就涉及JVM很多內(nèi)容了,和本文主題無關,后續(xù)會另開一篇講字節(jié)碼指令。這里大家主要看static代碼塊中有哪些指令,明白枚舉為什么會線程安全即可。

          瀏覽 31
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色老板在线精品 | 欧美丰满少妇人妻精品 | 大鸡巴久久久久久久久久 | 淫色在线免费视频 | 日韩黄色视频大全 |