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

          鵝廠一面,有關(guān) ThreadLocal 的一切

          共 4957字,需瀏覽 10分鐘

           ·

          2022-05-14 02:47


          今天和大家分享的是面試常駐嘉賓:ThreadLocal

          當(dāng)初鵝廠一面就有問到它,問題的答案在下面正文的第 2 點。


          1. 底層結(jié)構(gòu)

          ThreadLocal 底層有一個默認(rèn)容量為 16 的數(shù)組組成,k 是 ThreadLocal 對象的引用,v 是要放到 TheadLocal 的值

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

          void?createMap(Thread?t,?T?firstValue)?{
          ????t.threadLocals?=?new?ThreadLocalMap(this,?firstValue);
          }

          ThreadLocalMap(ThreadLocal?firstKey,?Object?firstValue)?{
          ????table?=?new?Entry[INITIAL_CAPACITY];
          ????int?i?=?firstKey.threadLocalHashCode?&?(INITIAL_CAPACITY?-?1);
          ????table[i]?=?new?Entry(firstKey,?firstValue);
          ????size?=?1;
          ????setThreshold(INITIAL_CAPACITY);
          }

          數(shù)組類似為 HashMap,對哈希沖突的處理不是用鏈表/紅黑樹處理,而是使用鏈地址法,即嘗試順序放到哈希沖突下標(biāo)的下一個下標(biāo)位置。

          該數(shù)組也可以進(jìn)行擴(kuò)容。

          2. 工作原理

          一個 ThreadLocal 對象維護(hù)一個 ThreadLocalMap 內(nèi)部類對象,ThreadLocalMap 對象才是存儲鍵值的地方。

          更準(zhǔn)確的說,是 ThreadLocalMap 的 Entry 內(nèi)部類是存儲鍵值的地方

          見源碼 set(),createMap() 可知。

          因為一個 Thread 對象維護(hù)了一個 ThreadLocal.ThreadLocalMap 成員變量,且 ThreadLocal 設(shè)置值時,獲取的 ThreadLocalMap 正是當(dāng)前線程對象的 ThreadLocalMap

          //?獲取?ThreadLocalMap?源碼
          ThreadLocalMap?getMap(Thread?t)?{
          ????return?t.threadLocals;
          }

          所以每個線程對 ThreadLocal 的操作互不干擾,即 ThreadLocal 能實現(xiàn)線程隔離

          3. 使用

          ThreadLocal?threadLocal?=?new?ThreadLocal<>();
          threadLocal.set("七淅在學(xué)Java");
          Integer?i?=?threadLocal.get()
          //?i?=?七淅在學(xué)Java

          4. 為什么 ThreadLocal.ThreadLocalMap 底層是長度 16 的數(shù)組呢?

          對 ThreadLocal 的操作見第 3 點,可以看到 ThreadLocal 每次 set 方法都是對同個 key(因為是同個 ThreadLocal 對象,所以 key 肯定都是一樣的)進(jìn)行操作。

          如此操作,看似對 ThreadLocal 的操作永遠(yuǎn)只會存 1 個值,那用長度為 1 的數(shù)組它不香嗎?為什么還要用 16 長度呢?

          好了,其實這里有個需要注意的地方,ThreadLocal 是可以存多個值的

          那怎么存多個值呢?看如下代碼:

          //?在主線程執(zhí)行以下代碼:
          ThreadLocal?threadLocal?=?new?ThreadLocal<>();
          threadLocal.set("七淅在學(xué)Java");
          ThreadLocal?threadLocal2?=?new?ThreadLocal<>();
          threadLocal2.set("七淅在學(xué)Java2");

          按代碼執(zhí)行后,看著是 new 了 2 個 ThreadLocal 對象,但實際上,數(shù)據(jù)的存儲都是在同一個 ThreadLocal.ThreadLocalMap 上操作的

          再次強(qiáng)調(diào):ThreadLocal.ThreadLocalMap 才是數(shù)據(jù)存取的地方,ThreadLocal 只是 api 調(diào)用入口)。真相在 ThreadLocal 類源碼的 getMap()

          因此上述代碼最終結(jié)果就是一個 ThreadLocalMap 存了 2 個不同 ThreadLocal 對象作為 key,對應(yīng) value 為 七淅在學(xué)Java、七淅在學(xué)Java2。

          我們再看下 ThreadLocal 的 set 方法

          public?void?set(T?value)?{
          ????Thread?t?=?Thread.currentThread();
          ????//?這里每次?set?之前,都會調(diào)用?getMap(t)?方法,t?是當(dāng)前調(diào)用?set?方法的線程
          ????ThreadLocalMap?map?=?getMap(t);
          ????if?(map?!=?null)
          ????????map.set(this,?value);
          ????else
          ????????createMap(t,?value);
          }

          //?重點:返回調(diào)用 set 方法的線程(例子是主線程)的 ThreadLocal 對象。??
          //?所以不管 api 調(diào)用方 new 多少個 ThreadLocal 對象,它永遠(yuǎn)都是返回調(diào)用線程(例子是主線程)的 ThreadLocal.ThreadLocalMap 對象供調(diào)用線程去存取數(shù)據(jù)。
          ThreadLocalMap?getMap(Thread?t)?{
          ????return?t.threadLocals;
          }

          //?t.threadLocals?的聲明如下
          ThreadLocal.ThreadLocalMap?threadLocals?=?null;

          //?僅有一個構(gòu)造方法
          public?ThreadLocal()?{
          }

          5. 數(shù)據(jù)存放在數(shù)組中,那如何解決 hash 沖突問題

          使用鏈地址法解決。

          具體怎么解決呢?看看執(zhí)行 get、set 方法的時候:

          • set:
            • 且數(shù)組的 key 等于該 ThreadLocal,則覆蓋該位置元素
            • 否則就找下一個空位置,直到找到空或者 key 相等為止。
            • 根據(jù) ThreadLocal 對象的 hash 值,定位到 ThreadLocalMap 數(shù)組中的位置。
            • 如果位置無元素則直接放到該位置
            • 如果有元素
          • get:
            • 根據(jù) ThreadLocal 對象的 hash 值,定位到 ThreadLocalMap 數(shù)組中的位置。
            • 如果不一致,就判斷下一個位置
            • 否則則直接取出
          //?數(shù)組元素結(jié)構(gòu)
          Entry(ThreadLocal?k,?Object?v)?{
          ????super(k);
          ????value?=?v;
          }

          6. ThreadLocal 的內(nèi)存泄露隱患

          三個前置知識:

          • ThreadLocal 對象維護(hù)一個 ThreadLocalMap 內(nèi)部類
          • ThreadLocalMap 對象又維護(hù)一個 Entry 內(nèi)部類,并且該類繼承弱引用 WeakReference>,用來存放作為 key 的 ThreadLocal 對象(可見最下方的 Entry 構(gòu)造方法源碼),可見最后的源碼部分。
          • 不管當(dāng)前內(nèi)存空間足夠與否,GC 時 JVM 會回收弱引用的內(nèi)存

          因為 ThreadLocal 作為弱引用被 Entry 中的 Key 變量引用,所以如果 ThreadLocal 沒有外部強(qiáng)引用來引用它,那么 ThreadLocal 會在下次 JVM 垃圾收集時被回收。

          這個時候 Entry 中的 key 已經(jīng)被回收,但 value 因為是強(qiáng)引用,所以不會被垃圾收集器回收。這樣 ThreadLocal 的線程如果一直持續(xù)運(yùn)行,value 就一直得不到回收,導(dǎo)致發(fā)生內(nèi)存泄露。

          如果想要避免內(nèi)存泄漏,可以使用 ThreadLocal 對象的 remove() 方法

          7. 為什么 ThreadLocalMap 的 key 是弱引用

          static?class?ThreadLocalMap?{
          ????static?class?Entry?extends?WeakReference<ThreadLocal>?{
          ????????Object?value;

          ????????Entry(ThreadLocal?k,?Object?v)?{
          ????????????super(k);
          ????????????value?=?v;
          ????????}
          ????}
          }

          為什么要這樣設(shè)計,這樣分為兩種情況來討論:

          • key 使用強(qiáng)引用:只有創(chuàng)建 ThreadLocal 的線程還在運(yùn)行,那么 ThreadLocalMap 的鍵值就都會內(nèi)存泄漏,因為 ThreadLocalMap 的生命周期同創(chuàng)建它的 Thread 對象。
          • key 使用弱引用:是一種挽救措施,起碼弱引用的值可以被及時 GC,減輕內(nèi)存泄漏。另外,即使沒有手動刪除,作為鍵的 ThreadLocal 也會被回收。因為 ThreadLocalMap 調(diào)用 set、get、remove 時,都會先判斷之前該 value 對應(yīng)的 key 是否和當(dāng)前調(diào)用的 key 相等。如果不相等,說明之前的 key 已經(jīng)被回收了,此時 value 也會被回收。因此 key 使用弱引用是最優(yōu)的解決方案。

          8. 父子線程如何共享 ThreadLocal 數(shù)據(jù)

          1. 主線程創(chuàng)建 InheritableThreadLocal 對象時,會為 t.inheritableThreadLocals 變量創(chuàng)建 ThreadLocalMap,使其初始化。其中 t 是當(dāng)前線程,即主線程
          2. 創(chuàng)建子線程時,在 Thread 的構(gòu)造方法,會檢查其父線程的 inheritableThreadLocals 是否為 null。從第 1 步可知不為 null,接著 將父線程的 inheritableThreadLocals 變量值復(fù)制給這個子線程。
          3. InheritableThreadLocal 重寫了 getMap, createMap, 使用的都是 Thread.inheritableThreadLocals 變量

          如下:

          public?class?InheritableThreadLocal<T>?extends?ThreadLocal<T>?

          第 1 步:對?InheritableThreadLocal?初始化
          public?class?InheritableThreadLocal<T>?extends?ThreadLocal<T>?
          {
          ????void?createMap(Thread?t,?T?firstValue)?{
          ????????t.inheritableThreadLocals?=?new?ThreadLocalMap(this,?firstValue);
          ????}
          }

          第?2?步:創(chuàng)建子線程時,判斷父線程的 inheritableThreadLocals 是否為空。非空進(jìn)行復(fù)制
          //?Thread?構(gòu)造方法中,一定會執(zhí)行下面邏輯
          if?(inheritThreadLocals?&&?parent.inheritableThreadLocals?!=?null)
          ????this.inheritableThreadLocals?=
          ????????ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

          第?3?步:使用對象為第?1?步創(chuàng)建的?inheritableThreadLocals?對象
          public?class?InheritableThreadLocal<T>?extends?ThreadLocal<T>?{
          ????ThreadLocalMap?getMap(Thread?t)?{
          ???????return?t.inheritableThreadLocals;
          ????}
          }

          //?示例:
          //?結(jié)果:能夠輸出「父線程-七淅在學(xué)Java」
          ThreadLocal?threadLocal?=?new?InheritableThreadLocal();
          threadLocal.set("父線程-七淅在學(xué)Java");
          Thread?t?=?new?Thread(()?->?System.out.println(threadLocal.get()));
          t.start();

          //?結(jié)果:null,不能夠輸出「子線程-七淅在學(xué)Java」
          ThreadLocal?threadLocal2?=?new?InheritableThreadLocal();
          Thread?t2?=?new?Thread(()?->?{
          ????threadLocal2.set("子線程-七淅在學(xué)Java");
          });
          t2.start();
          System.out.println(threadLocal2.get());


          覺得文章不錯的話,可以給個三連嗎?

          瀏覽 21
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  无码区精品区一区二区三区 | 婷婷天网站 | 色五月婷婷中文字幕 | 国产特级毛片AAAAAA喷潮 | 成人国产经典视频 |