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

          百度二面:ThreadLocal 傳參如何使用?

          共 12133字,需瀏覽 25分鐘

           ·

          2021-09-21 20:10


          來源:cnblogs.com/aspirant/
          p/8991010.html

          • 一、目錄
          • 二、ThreadLocal 是什么?有什么用?
            • ThreadLocal 到底有什么用?
            • ThreadLocal 源碼簡要總結?
            • hreadLocal 為什么會導致內存泄漏?
            • 什么是弱引用?
            • 看一看 ThreadLocal 源碼

          ThreadLocal 也可以跟蹤一個請求,從接收請求,處理請求,到返回請求,只要線程不銷毀,就可以在線程的任何地方,調用這個參數。

          這兩天,粉絲群一位網友分享了百度二面的題目:如何使用 ThreadLocal 傳參,引起了眾多群友的討論,其中有不少網友的回答很有深度。這里我給大家分享一篇文章,看完希望對大家能有所突破。如有不懂,或疑問,歡迎加我微信:codedq,進行粉絲群進行技術交流!

          我先對 ThreadLocal 做個小總結:

          • JVM 利用設置 ThreadLocalMap 的 Key 為弱引用,來避免內存泄露。
          • JVM 利用調用 remove、get、set 方法的時候,回收弱引用。
          • 當 ThreadLocal 存儲很多 Key 為 null 的 Entry 的時候,而不再去調用 remove、get、set 方法,那么將導致內存泄漏。
          • 當使用 static ThreadLocal 的時候,延長 ThreadLocal 的生命周期,那也可能導致內存泄漏。因為,static 變量在類未加載的時候,它就已經加載,當線程結束的時候,static 變量不一定會回收。那么,比起普通成員變量使用的時候才加載,static 的生命周期加長將更容易導致內存泄漏危機。

          「事實上,在 ThreadLocalMap 中的 set/getEntry 方法中,會對 key 為 null(也即是 ThreadLocal 為 null)進行判斷,如果為 null 的話,那么是會對 value 置為 null 的。我們也可以通過調用 ThreadLocal 的 remove 方法進行釋放!」

          threadlocal 里面使用了一個存在弱引用的 map,當釋放掉 threadlocal 的強引用以后,map 里面的 value 卻沒有被回收。而這塊 value 永遠不會被訪問到了。所以存在著內存泄露。最好的做法是將調用 threadlocal 的 remove 方法。

          在 threadlocal 的生命周期中,都存在這些引用??聪聢D: 實線代表強引用,虛線代表弱引用。

          threadlocal 的生命周期

          每個 thread 中都存在一個 map, map 的類型是 ThreadLocal.ThreadLocalMap。Map 中的 key 為一個 threadlocal 實例。這個 Map 的確使用了弱引用,不過弱引用只是針對 key。每個 key 都弱引用指向 threadlocal。當把 threadlocal 實例置為 null 以后,沒有任何強引用指向 threadlocal 實例,所以 threadlocal 將會被 gc 回收。但是,我們的 value 卻不能回收,因為存在一條從 current thread 連接過來的強引用。只有當前 thread 結束以后,current thread 就不會存在棧中,強引用斷開,Current Thread, Map, value 將全部被 GC 回收。

          所以得出一個結論就是只要這個線程對象被 gc 回收,就不會出現(xiàn)內存泄露,但**「在threadLocal 設為 null 和線程結束這段時間不會被回收的,就發(fā)生了我們認為的內存泄露」** 。其實這是一個對概念理解的不一致,也沒什么好爭論的。最要命的是線程對象不被回收的情況,這就發(fā)生了真正意義上的內存泄露。「比如使用線程池的時候,線程結束是不會銷毀的,會再次使用的。就可能出現(xiàn)內存泄露」

          PS:Java為了最小化減少內存泄露的可能性和影響,在 ThreadLocal 的 get,set 的時候都會清除線程 Map 里所有 key 為 null 的 value。所以最怕的情況就是,threadLocal 對象設 null 了,開始發(fā)生“內存泄露”,然后使用線程池,這個線程結束,線程放回線程池中不銷毀,這個線程一直不被使用,或者分配使用了又不再調用 get,set 方法,那么這個期間就會發(fā)生真正的內存泄露。

          一、目錄

          1. ThreadLocal 是什么?有什么用?
          2. ThreadLocal 源碼簡要總結?
          3. ThreadLocal 為什么會導致內存泄漏?

          推薦下自己做的 Spring Boot 的實戰(zhàn)項目:

          https://github.com/YunaiV/ruoyi-vue-pro

          二、ThreadLocal 是什么?有什么用?

          引入話題:在并發(fā)條件下,如何正確獲得共享數據?舉例:假設有多個用戶需要獲取用戶信息,一個線程對應一個用戶。在 mybatis 中,session 用于操作數據庫,那么設置、獲取操作分別是 session.set()、session.get(),如何保證每個線程都能正確操作達到想要的結果?

          /*
           * 回顧 synchronized 在多線程共享線程的問題
           */

          public class ThreadLocalOne {
               volatile Person person=new Person();
           
               public  synchronized String setAndGet(String name){
                    //System.out.print(Thread.currentThread().getName()+:);
                     person.name=name;
                     //模擬網絡延遲
                     try {
                          TimeUnit.SECONDS.sleep(2);
                     } catch (InterruptedException e) {
                          e.printStackTrace();
                     }
                     return person.name;
               }
           
               public static void main(String\[\] args) {
                     ThreadLocalOne  threadLocal\=new ThreadLocalOne();
                     new Thread(()->System.out.println(threadLocal.setAndGet(arron)),t1).start();
                     new Thread(()->System.out.println(threadLocal.setAndGet(tony)),t2).start();
               }
          }
           
          class Person{
               String name\=tom;
               public Person(String name) {
                     this.name=name;
               }
           
               public Person(){}
          }

          運行結果:

          synchronized
          t1:tony
          t2:tony

          synchronized
          t1:arron
          t2:tony

          步驟分析:

          1. 無 synchronized 的時候,因為非原子操作,顯然不是預想結果,可參考我關于 synchronized 的討論。
          2. 現(xiàn)在,我們的需求是:每個線程獨立的設置獲取 person 信息,不被線程打擾。
          3. 因為,person 是共享數據,用同步互斥鎖 synchronized,當一個線程訪問共享數據的時候,其他線程堵塞,不再多余贅述。

          通過舉例問題,可能大家又會很疑惑?

          mybatis、hibernate 是如何實現(xiàn)的呢?

          synchronized 不會很消耗資源,當成千上萬個操作的時候,承受并發(fā)不說,數據返回延遲如何確保用戶體驗?

          ThreadLocal 是什么?有什么用?

          /**
           * 談談 ThreadLocal 的作用
           */

          public class ThreadLocalThree {
               ThreadLocal<Person> threadLocal=new ThreadLocal<Person>();
               public String setAndGet(String name){
                     threadLocal.set(new Person(name));
                     try {
                          TimeUnit.SECONDS.sleep(2);
                     } catch (InterruptedException e) {
                          e.printStackTrace();
                     }
                     return threadLocal.get().name;
               }
           
               public static void main(String[] args) {
                     ThreadLocalThree  threadLocal=new ThreadLocalThree();
                     new Thread(()->System.out.println(t1:+threadLocal.setAndGet(arron)),t1).start();
                     new Thread(()->System.out.println(t2:+threadLocal.setAndGet(tony)),t2).start();
               }
          }

          運行結果:

          t1:arron
          t2:tony

          分析:

          1、根據預期結果,那 ThreadLocal 到底是什么?

          回顧 Java 內存模型

          在虛擬機中,堆內存用于存儲共享數據(實例對象),堆內存也就是這里說的主內存。

          每個線程將會在堆內存中開辟一塊空間叫做線程的工作內存,附帶一塊緩存區(qū)用于存儲共享數據副本。那么,共享數據在堆內存當中,線程通信就是通過主內存為中介,線程在本地內存讀并且操作完共享變量操作完畢以后,把值寫入主內存。

          1. ThreadLocal 被稱為線程局部變量,說白了,他就是線程工作內存的一小塊內存,用于存儲數據。
          2. 那么,ThreadLocal.set()、ThreadLocal.get() 方法,就相當于**「把數據存儲于線程本地,取也是在本地內存讀取」** 。「就不會像 synchronized 需要頻繁的修改主內存的數據,再把數據復制到工作內存,也大大提高訪問效率」 。

          ThreadLocal 到底有什么用?

          1. 回到最開始的舉例,也就等價于 mabatis、hibernate 為什么要使用threadlocal 來存儲 session?
          2. 作用一**:因為線程間的數據交互是通過工作內存與主存的頻繁讀寫完成通信,然而存儲于線程本地內存,提高訪問效率,避免線程阻塞造成 cpu 吞吐率下降** 。
          3. 作用二:「在多線程中,每一個線程都需要維護 session,輕易完成對線程獨享資源的操作」 。

          總結:

          Threadlocal 是什么?在堆內存中,每個線程對應一塊工作內存,threadlocal 就是工作內存的一小塊內存。

          Threadlocal 有什么用?threadlocal 用于存取線程獨享數據,提高訪問效率

          ThreadLocal 源碼簡要總結?

          那有同學可能還是有點云里霧里,感覺還是沒有吃透?那線程內部如何去保證線程獨享數據呢?

          在這里,我只做簡要總結,若有興趣,可參考文章尾部的文章鏈接。重點看 get、set 方法。

          public void set(T value) {
             Thread t = Thread.currentThread();
             ThreadLocalMap map = getMap(t);
             if (map != null)
                 map.set(this, value);
             else
                 createMap(t, value);
          }

          分析:

          1. 一個線程對應一個 ThreadLocalMap ,可以存儲多個 ThreadLocal 對象。
          2. ThreadLocal 對象作為key、獨享數據作為value。
          3. ThreadLocalMap 可參考 HashMap,在 ThreadMap 里面存在 Entry 數組也就是一個 Entry 一個鍵值對。
          public T get() {
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t);
              if (map != null) {
                  ThreadLocalMap.Entry e = map.getEntry(this);
                  if (e != null) {
                      @SuppressWarnings(unchecked)
                      T result = (T)e.value;
                      return result;
                  }
              }
              return setInitialValue();
          }

          分析:

          1. 一個線程對應一個 ThreadLocalMap,get() 就是當前線程獲取自己的 ThreadLocalMap。
          2. 線程根據使用那一小塊的 threadlocal,根據 ThreadLocal 對象作為 key,去獲取存儲于 ThreadLocalMap 中的值。

          總結:

          回顧一下,我們在單線程中如何使用 HashMap 的?hashMap 根據數組 + 鏈表來實現(xiàn) HashMap,一個 key 對應一個 value。那么,我們抽象一下, Threadlocal 也相當于在多線程中的一種 HashMap 用法,相當于對 ThradLocal 的操作也就如單線程操作一樣。

          總之,ThreadLocal 就是堆內存的一塊小內存,它用 ThreadLocalMap 維護 ThreadLocal 對象作為 key,獨享數據作為 value 的東西。

          hreadLocal 為什么會導致內存泄漏?

          「synchronized 是用時間換空間(犧牲時間)、ThreadLocal 是用空間換時間(犧牲空間)」 ,為什么這么說?

          「因為 synchronized 操作數據,只需要在主存存一個變量即可,就阻塞等共享變量,而 ThreadLocal 是每個線程都創(chuàng)建一塊小的堆工作內存」 。顯然,印證了上面的說法。

          一個線程對應一塊工作內存,線程可以存儲多個 ThreadLocal。那么假設,開啟 1 萬個線程,每個線程創(chuàng)建 1 萬個 ThreadLocal,也就是每個線程維護 1 萬個 ThreadLocal 小內存空間,而且當線程執(zhí)行結束以后,假設這些 ThreadLocal 里的 Entry 還不會被回收,那么將很容易導致堆內存溢出。

          怎么辦?難道 JVM 就沒有提供什么解決方案嗎?

          ThreadLocal 當然有想到,所以他們把 ThreadLocal 里的 Entry 設置為弱引用,當垃圾回收的時候,回收 ThreadLocal。

          什么是弱引用?

          1. Key 使用強引用:也就是上述說的情況,引用 ThreadLocal 的對象被回收了,ThreadLocal 的引用 ThreadLocalMap 的 Key 為強引用并沒有被回收,如果不手動回收的話,ThreadLocal 將不會回收那么將導致內存泄漏。
          2. Key 使用弱引用:引用的 ThreadLocal 的對象被回收了,「ThreadLocal的引用 ThreadLocalMap 的 Key 為弱引用,如果內存回收,那么將ThreadLocalMap 的 Key 將會被回收,ThreadLocal 也將被回收。value 在ThreadLocalMap 調用 get、set、remove 的時候就會被清除」 。
          3. 比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長,如果都沒有手動刪除對應key,都會導致內存泄漏,但是使用弱引用可以多一層保障:「弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除」 。

          那按你這么說,既然 JVM 有保障了,還有什么內存泄漏可言?

          ThreadLocalMap 使用 ThreadLocal 對象作為弱引用,當垃圾回收的時候,ThreadLocalMap 中 Key 將會被回收,也就是將 Key 設置為 null 的 Entry。「如果線程遲遲無法結束,也就是 ThreadLocal 對象將一直不會回收,回顧到上面存在很多線程 + TheradLocal,那么也將導致內存泄漏。(內存泄露的重點)」

          其實,在 ThreadLocal 中,當調用 remove、get、set 方法的時候,會清除為 null 的弱引用,也就是回收 ThreadLocal。

          ThreadLocal 提供一個線程(Thread)局部變量,訪問到某個變量的每一個線程都擁有自己的局部變量。說白了,ThreadLocal 就是想在多線程環(huán)境下去保證成員變量的安全。

          「ThreadLocal 提供的方法」

          ThreadLocal API

          ThreadLocal API

          ?

          「對于 ThreadLocal 而言,常用的方法,就是 get/set/initialValue 方法。」

          ?

          「我們先來看一個例子」

          ThreadLocal 例子

          「運行結果」

          ThreadLocal 例子運行結果

          是你想象中的結果么?

          「很顯然,在這里,并沒有通過 ThreadLocal 達到線程隔離的機制,可是ThreadLocal不是保證線程安全的么?這是什么鬼?」

          「雖然,ThreadLocal 讓訪問某個變量的線程都擁有自己的局部變量,但是如果這個局部變量都指向同一個對象呢?這個時候 ThreadLocal 就失效了。仔細觀察下圖中的代碼,你會發(fā)現(xiàn),threadLocal 在初始化時返回的都是同一個對象 a!」

          看一看 ThreadLocal 源碼

          「我們直接看最常用的set操作:」

          set操作
          線程局部變量
          createMap

          「你會看到,set 需要首先獲得當前線程對象 Thread;」

          「然后取出當前線程對象的成員變量 ThreadLocalMap;」

          「如果 ThreadLocalMap 存在,那么進行 KEY/VALUE 設置,KEY 就是 ThreadLocal;」

          「如果 ThreadLocalMap 沒有,那么創(chuàng)建一個;」

          「說白了,當前線程中存在一個 Map 變量,KEY 是 ThreadLocal,VALUE 是你設置的值?!?/strong>

          「看一下 get 操作:」

          get 操作

          ?

          「這里其實揭示了 ThreadLocalMap 里面的數據存儲結構,從上面的代碼來看,ThreadLocalMap 中存放的就是 Entry,Entry 的 KEY 就是 ThreadLocal,VALUE 就是值?!?/strong>

          ?

          「ThreadLocalMap.Entry:」

          弱引用

          「在 JAVA 里面,存在強引用、弱引用、軟引用、虛引用。這里主要談一下強引用和弱引用?!?/strong>

          強引用,就不必說了,類似于:

          A a = new A();

          B b = new B();

          考慮這樣的情況:

          C c = new C(b);  
          b = null

          考慮下 GC 的情況。要知道 b 被置為 null,那么是否意味著一段時間后 GC 工作可以回收 b 所分配的內存空間呢?答案是否定的,因為即便b被置為 null,但是 c 仍然持有對 b 的引用,而且還是強引用,所以 GC 不會回收 b 原先所分配的空間!既不能回收利用,又不能使用,這就造成了**「內存泄露」** 。

          那么如何處理呢?

          「可以 c = null;也可以使用弱引用?。╓eakReference w = new WeakReference(b);)」

          分析到這里,我們可以得到:

          內存結構圖

          「這里我們思考一個問題:ThreadLocal 使用到了弱引用,是否意味著不會存在內存泄露呢?」

          「首先來說,如果把 ThreadLocal 置為 null,那么意味著 Heap 中的 ThreadLocal 實例不在有強引用指向,只有弱引用存在,因此 GC 是可以回收這部分空間的,也就是 key 是可以回收的。但是 value 卻存在一條從 Current Thread 過來的強引用鏈。因此只有當 Current Thread 銷毀時,value 才能得到釋放?!?/strong>

          「因此,只要這個線程對象被 gc 回收,就不會出現(xiàn)內存泄露,但在 threadLocal 設為 null 和線程結束這段時間內不會被回收的,就發(fā)生了我們認為的內存泄露。最要命的是線程對象不被回收的情況,比如使用線程池的時候,線程結束是不會銷毀的,再次使用的,就可能出現(xiàn)內存泄露?!?/strong>

          「那么如何有效的避免呢?」

          「事實上,在 ThreadLocalMap 中的 set/getEntry 方法中,會對 key 為null(也即是 ThreadLocal 為 null)進行判斷,如果為 null 的話,那么是會對 value 置為 null 的。我們也可以通過調用 ThreadLocal 的 remove 方法進行釋放!」

          - END -


          推薦閱讀:

          世界的真實格局分析,地球人類社會底層運行原理

          不是你需要中臺,而是一名合格的架構師(附各大廠中臺建設PPT)

          企業(yè)IT技術架構規(guī)劃方案

          論數字化轉型——轉什么,如何轉?

          華為干部與人才發(fā)展手冊(附PPT)

          企業(yè)10大管理流程圖,數字化轉型從業(yè)者必備!

          【中臺實踐】華為大數據中臺架構分享.pdf

          華為的數字化轉型方法論

          華為如何實施數字化轉型(附PPT)

          超詳細280頁Docker實戰(zhàn)文檔!開放下載

          華為大數據解決方案(PPT)

          瀏覽 34
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                    免费特级黄毛片 | 亚洲欧洲一区二区 | 丁香六月婷婷综合 | 日本67194 | 三级片99|