<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>

          聊聊如何實現(xiàn)更優(yōu)雅的單例?

          共 8142字,需瀏覽 17分鐘

           ·

          2022-06-21 03:28

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享 
          關(guān)注


          閱讀本文大概需要 5 分鐘。

          來自:https://blog.csdn.net/weixin_43178828

          前言

          閱讀源碼的時候總會發(fā)現(xiàn)大神 對于單例 Singleton, 比如數(shù)據(jù)庫連接對象 比如線程池 還有spring的容器 都有些細(xì)微的操作 但不知道啥含義 今天我們聊聊優(yōu)雅的(效率高 數(shù)據(jù)安全的)單例怎么實現(xiàn)
          向著優(yōu)雅Java的道路前進(jìn)!

          從最簡單的開始 getInstance

          我們總是能看到getInstance方法 why?我一開始也不理解 為啥不直接調(diào)用這個實例呢 我直接訪問那個實例對象就好了啊
          隨著讀源碼的深入 我萌生出幾個問題:
          • 你訪問這個實例對象的時候 這個實例真的已經(jīng)創(chuàng)建出來了嗎?
          • 這個實例對象是怎么創(chuàng)建出來的?創(chuàng)建的機(jī)制其實各有不同
            • 比如所謂懶漢式的創(chuàng)建 是需要你 依賴他這個實例的時候 才會觸發(fā) 從而創(chuàng)建單例的 為的是節(jié)省內(nèi)存空間——沒人用我創(chuàng)建他出來干嘛?
            • 所以 在你獲取對象之前 是不是有個觸發(fā)的東西呢?說白了前邊還有些代碼邏輯實現(xiàn)懶漢式的思路
          • 如果能夠直接訪問到這個實例對象 怎么保證是單例呢?我上次訪問的和這次 是同一個對象?
          • 如何保證創(chuàng)建的是單例 尤其是多線程的環(huán)境下
          基于上述的問題 一個經(jīng)典的實現(xiàn)套路:
          public class MySingleton {
           private static MySingleton instance = new MySingleton();
           
           private Singleton() {
           }
            
           public static MySingleton getInstance() {
            return instance;
           } 
          }
          這樣我們使用只需要MySingleton singleton = MySingleton.getInstance();
          注意一個細(xì)節(jié) 為了保證單例 我們這里使用static 保證單例 和Class對象綁定在一起 所以必能訪問唯一的實例

          復(fù)雜單例的創(chuàng)建過程——用串行化的static代碼塊解決

          我們拓展一下 假設(shè)創(chuàng)建的過程很復(fù)雜 可能需要別的bean來輔助 即我依賴別的類的實例對象 輔助我完成創(chuàng)建 還有很多細(xì)節(jié)的創(chuàng)建過程 這時應(yīng)該怎么進(jìn)行單例的初始化呢?
          使用static代碼塊 因為這個代碼塊執(zhí)行順序是嚴(yán)格串行的,JLS標(biāo)準(zhǔn)保證了這一點(diǎn)
          所以不會有虛擬機(jī)優(yōu)化 指令重排序的問題 也不會有多線程的數(shù)據(jù)安全問題
          public class MySingleton {
           private static MySingleton instance = null;
           private static OtherSingleton helper = null;
           static{
            helper = OtherSingleton.getInstance();
            instance = MySingleton(helper);
           }

           private MySingleton(OtherSingleton helper) {
            if(helper) this.helper = helper;
            else throw new MyException("OtherSingleton getInstance failed");
           }
            
           public static MySingleton getInstance() {
            return instance;
           } 
          }
          可以發(fā)現(xiàn) static的串行化 保證我創(chuàng)建單例的時候 依賴的helper是能拿得到的(這個具體由OtherSingleton 的getInstance負(fù)責(zé) 我們這里最多加一層檢查 攔截拋異常 ) 不會出現(xiàn) 因為多線程 導(dǎo)致創(chuàng)建的時候 helper拿不到的情況。。
          getInstance里邊也可以添加獨(dú)特的東西(懶漢式我們后邊再聊)

          懶漢式(延遲創(chuàng)建)

          基本思路,訪問getInstance
          • 如果沒有創(chuàng)建 則 開始創(chuàng)建 并返回單例
          • 已經(jīng)創(chuàng)建 則直接返回
          public class MySingleton {
           private static MySingleton instance = null;
           private static OtherSingleton helper = null;
           private MySingleton(OtherSingleton helper) {
            if(helper) this.helper = helper;
            else throw new MyException("OtherSingleton getInstance failed");
           }
           
           public static MySingleton getInstance() {
            if(instance == null)
             instance = new MySingleton(OtherSingleton.getInstance());
            return instance;
           } 
          }
          但是問題在于 多線程情況下 這個if的判斷也未必準(zhǔn)確 假設(shè)同時有兩個線程都進(jìn)到這個if里邊執(zhí)行 就會創(chuàng)建出兩個實例 而不是單例 而為什么有兩個能進(jìn)去?一個線程創(chuàng)建單例的時候 另外一個直接進(jìn)來了(那個時候單例還沒創(chuàng)造出來 instance == null 當(dāng)然進(jìn)的來)

          內(nèi)存泄漏?

          有人覺得 java有內(nèi)存回收機(jī)制 沒人用那個多余的單例 就會被回收 問題是 這么執(zhí)行會導(dǎo)致兩個單例都會被用到 創(chuàng)建的時候有多少個線程進(jìn)去if里邊 那就有多少單例產(chǎn)生并被使用 所以導(dǎo)致嚴(yán)重的內(nèi)存泄漏!
          解決方案:
          我們認(rèn)為這個if里邊是個臨界區(qū)域 就只能有一個線程在里邊才對!所以可以粗暴的使用synchronized 讓整個代碼塊順序執(zhí)行 就類似static代碼塊一樣:
          public class MySingleton {
           private static MySingleton instance = null;
           private static OtherSingleton helper = null;
           private MySingleton(OtherSingleton helper) {
            if(helper) this.helper = helper;
            else throw new MyException("OtherSingleton getInstance failed");
           }
           
           public static synchronized MySingleton getInstance() {
            if(instance == null)
             instance = new MySingleton(OtherSingleton.getInstance());
            return instance;
           } 
          }

          更高的性能 double check locking

          整個上鎖 由于鎖粒度不夠細(xì) 導(dǎo)致性能比較低 因此思路是盡量降低鎖的粒度 范圍:
          public class MySingleton {
           private static MySingleton instance = null;
           private static OtherSingleton helper = null;
           private MySingleton(OtherSingleton helper) {
            if(helper) this.helper = helper;
            else throw new MyException("OtherSingleton getInstance failed");
           }
           
           public static synchronized MySingleton getInstance() {
            if(instance == null){
             synchronized (MySingleton.class){
              if(instance == null){
               instance = new MySingleton(OtherSingleton.getInstance());
              } 
             }
            }
             
            return instance;
           } 
          }
          這里 我們設(shè)定MySingleton.class對象 作為臨界 意圖明顯 class對象唯一 所以我們鎖類 這樣就保證了單線程的創(chuàng)建實例 其實和static靜態(tài)塊異曲同工 因為static也是類初始化的時候執(zhí)行的 同樣也是保證串行 綁定了class對象的執(zhí)行

          可見性 volatile

          但這里其實還有個問題 就是創(chuàng)建實例對象的一瞬間 真的別的線程就能立馬知道了嘛(可見性)?當(dāng)然是不可能的 注意 我們電腦CPU和內(nèi)存的數(shù)據(jù)一致性 或者說緩存一致性也是不一定有保證的 畢竟存在頻率(訪問速率)的差異 自然會存在緩存沒有更新的情況
          比如這里的實例對象變量instance!多線程在下一個指令周期 搶到了CPU計算的時間片 執(zhí)行 那個時候緩存默認(rèn)是不更新的
          除非 我們調(diào)用java的volatile 他自然是個native的關(guān)鍵字 底層依賴C來實現(xiàn)變量的可見性!所以我們終極的程序應(yīng)當(dāng)是:
          public class MySingleton {
           private volatile static MySingleton instance = null;
           private static OtherSingleton helper = null;
           private MySingleton(OtherSingleton helper) {
            if(helper) this.helper = helper;
            else throw new MyException("OtherSingleton getInstance failed");
           }
           
           public static synchronized MySingleton getInstance() {
            if(instance == null){
             synchronized (MySingleton.class){
              if(instance == null){
               instance = new MySingleton(OtherSingleton.getInstance());
              } 
             }
            }
             
            return instance;
           } 
          }

          另一種懶漢式創(chuàng)建單例——靜態(tài)內(nèi)部類

          既然是懶漢式 自然沒辦法直接用static來創(chuàng)建了 但是可不可能用另外一個類的static來保證懶漢式單例呢?
          public class MySingleton {
           private MySingleton(OtherSingleton helper) {
            if(helper) this.helper = helper;
            else throw new MyException("OtherSingleton getInstance failed");
           }
           
           public static class MySingletonAdapter {
            private static final MySingleton instance = new MySingleton(OtherSingleton.getInstance());
           } 
           public static getInstance() {
            return MySingletonAdapter.instance;
           }
          }
          這種方式被稱為:Initialization on demand holder

          序列化單例

          實現(xiàn)了Serializable接口的單例 序列化倒沒什么問題 但是反序列化時會產(chǎn)生新的實例對象 這里我們得改改readResolve()方法 使得返回的實例保證單例
          public class MySingleton {
           private static final long serialVersionUID = -3453453414141241L;
           private static MySingleton instance = new MySingleton(OtherSingleton.getInstance());
           
           private MySingleton(OtherSingleton helper) {
            if(helper) this.helper = helper;
            else throw new MyException("OtherSingleton getInstance failed");
           }
           
           private Object readResolve() {
            return instance;
           }
          }

          后記

          其實有沒有考慮過另一個問題 這里我們很快樂的使用了OtherSingleton.getInstance() 但是有沒有想過 系統(tǒng)剛開始一啟動 實例化 誰先誰后呢?假設(shè)Other是后邊才實例化的 前邊的MySingleton的創(chuàng)建不是吃癟了嘛???
          假設(shè)我們控制一個創(chuàng)建單例的串行順序 就好像玩游戲mod 有個依賴順序 設(shè)計一個排序 那看起來雖然很麻煩 應(yīng)該沒啥問題
          但是!如果 兩個互相依賴怎么辦?OtherSingleton初始化是需要MySingleton的 怎么辦呢?如果幾百個bean 初始化的時候互相依賴 該怎么解決?
          這時就需要 有一種思想可以用于 解決 各種由于依賴導(dǎo)致的問題 ——IOC(invertion of controll) 控制翻轉(zhuǎn)思想 其實際實現(xiàn)是通過依賴注入dependency injection。
          <END>
          推薦閱讀:
          一次簡單的 JVM 調(diào)優(yōu),拿去寫到簡歷里
          Spring壓軸題:當(dāng)循環(huán)依賴遇上Spring AOP
          互聯(lián)網(wǎng)初中高級大廠面試題(9個G)
          內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬并發(fā)、消息隊列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!
          ?戳閱讀原文領(lǐng)取!                                  朕已閱 
          瀏覽 16
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  色色干干 | 18禁网站禁片免费观看 | 国产乱伦无码精品 | 欧美艹逼视频 | 99热日韩 |