1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        聊聊如何實現(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的容器 都有些細微的操作 但不知道啥含義 今天我們聊聊優(yōu)雅的(效率高 數(shù)據(jù)安全的)單例怎么實現(xiàn)
        向著優(yōu)雅Java的道路前進!

        從最簡單的開始 getInstance

        我們總是能看到getInstance方法 why?我一開始也不理解 為啥不直接調(diào)用這個實例呢 我直接訪問那個實例對象就好了啊
        隨著讀源碼的深入 我萌生出幾個問題:
        • 你訪問這個實例對象的時候 這個實例真的已經(jīng)創(chuàng)建出來了嗎?
        • 這個實例對象是怎么創(chuàng)建出來的?創(chuàng)建的機制其實各有不同
          • 比如所謂懶漢式的創(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();
        注意一個細節(jié) 為了保證單例 我們這里使用static 保證單例 和Class對象綁定在一起 所以必能訪問唯一的實例

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

        我們拓展一下 假設(shè)創(chuàng)建的過程很復(fù)雜 可能需要別的bean來輔助 即我依賴別的類的實例對象 輔助我完成創(chuàng)建 還有很多細節(jié)的創(chuàng)建過程 這時應(yīng)該怎么進行單例的初始化呢?
        使用static代碼塊 因為這個代碼塊執(zhí)行順序是嚴格串行的,JLS標準保證了這一點
        所以不會有虛擬機優(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負責 我們這里最多加一層檢查 攔截拋異常 ) 不會出現(xiàn) 因為多線程 導(dǎo)致創(chuàng)建的時候 helper拿不到的情況。。
        getInstance里邊也可以添加獨特的東西(懶漢式我們后邊再聊)

        懶漢式(延遲創(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的判斷也未必準確 假設(shè)同時有兩個線程都進到這個if里邊執(zhí)行 就會創(chuàng)建出兩個實例 而不是單例 而為什么有兩個能進去?一個線程創(chuàng)建單例的時候 另外一個直接進來了(那個時候單例還沒創(chuàng)造出來 instance == null 當然進的來)

        內(nèi)存泄漏?

        有人覺得 java有內(nèi)存回收機制 沒人用那個多余的單例 就會被回收 問題是 這么執(zhí)行會導(dǎo)致兩個單例都會被用到 創(chuàng)建的時候有多少個線程進去if里邊 那就有多少單例產(chǎn)生并被使用 所以導(dǎo)致嚴重的內(nèi)存泄漏!
        解決方案:
        我們認為這個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

        整個上鎖 由于鎖粒度不夠細 導(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)建實例對象的一瞬間 真的別的線程就能立馬知道了嘛(可見性)?當然是不可能的 注意 我們電腦CPU和內(nèi)存的數(shù)據(jù)一致性 或者說緩存一致性也是不一定有保證的 畢竟存在頻率(訪問速率)的差異 自然會存在緩存沒有更新的情況
        比如這里的實例對象變量instance!多線程在下一個指令周期 搶到了CPU計算的時間片 執(zhí)行 那個時候緩存默認是不更新的
        除非 我們調(diào)用java的volatile 他自然是個native的關(guān)鍵字 底層依賴C來實現(xiàn)變量的可見性!所以我們終極的程序應(yī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壓軸題:當循環(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)?。?/span>                                  朕已閱 
        瀏覽 16
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            美女扒开粉嫩的尿囗 | www黄片 | 厨房呻吟嗯用力呀嗯啊视频 | 五月天婷婷久久 | 免费黄视频在线观看 | 亚洲少妇性爱 | 黄色国产视频 | 91精品视频在线播放 | 色婷婷精品国产一区二区三区 | 色色色色色综合 |