<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是什么?怎么用?為什么用它?有什么缺點(diǎn)

          共 9019字,需瀏覽 19分鐘

           ·

          2021-03-24 18:29

          作者:Sicimike

          blog.csdn.net/Baisitao_/article/details/100063561

          前言

          相信很多同學(xué)都聽(tīng)過(guò)ThreadLocal,即使沒(méi)用過(guò)也聽(tīng)過(guò)。但是要仔細(xì)一問(wèn)ThreadLocal是個(gè)啥,很多同學(xué)也不一定能說(shuō)清楚。本篇博客就是為了回答關(guān)于ThreadLocal的一系列靈魂拷問(wèn):ThreadLocal是個(gè)什么?怎么用?為什么要用它?它有什么缺點(diǎn)?怎么避免…

          ThreadLoacl是什么

          在了解ThreadLocal之前,我們先了解下什么是線(xiàn)程封閉

          把對(duì)象封閉在一個(gè)線(xiàn)程里,即使這個(gè)對(duì)象不是線(xiàn)程安全的,也不會(huì)出現(xiàn)并發(fā)安全問(wèn)題。

          實(shí)現(xiàn)線(xiàn)程封閉大致有三種方式:

          • Ad-hoc線(xiàn)程封閉:維護(hù)線(xiàn)程封閉性的職責(zé)完全由程序來(lái)承擔(dān),不推薦使用

          • 棧封閉:就是用(stack)來(lái)保證線(xiàn)程安全

          public void testThread() {
              StringBuilder sb = new StringBuilder();
              sb.append("Hello");
          }

          StringBuilder是線(xiàn)程不安全的,但是它只是個(gè)局部變量,局部變量存儲(chǔ)在虛擬機(jī)棧,虛擬機(jī)棧是線(xiàn)程隔離的,所以不會(huì)有線(xiàn)程安全問(wèn)題

          • ThreadLocal線(xiàn)程封閉:簡(jiǎn)單易用

          第三種方式就是通過(guò)ThreadLocal來(lái)實(shí)現(xiàn)線(xiàn)程封閉,線(xiàn)程封閉的指導(dǎo)思想是封閉,而不是共享。所以說(shuō)ThreadLocal是用來(lái)解決變量共享的并發(fā)安全問(wèn)題,多少有些不精確。

          使用

          JDK1.2開(kāi)始提供的java.lang.ThreadLocal的使用方式非常簡(jiǎn)單

          public class ThreadLocalDemo {
              
              public static void main(String[] args) throws InterruptedException {

                  final ThreadLocal<String> threadLocal = new ThreadLocal<>();
                  threadLocal.set("main-thread : Hello");
                  
                  Thread thread = new Thread(() -> {
                      // 獲取不到主線(xiàn)程設(shè)置的值,所以為null
                      System.out.println(threadLocal.get());
                      threadLocal.set("sub-thread : World");
                      System.out.println(threadLocal.get());
                  });
                  // 啟動(dòng)子線(xiàn)程
                  thread.start();
                  // 讓子線(xiàn)程先執(zhí)行完成,再繼續(xù)執(zhí)行主線(xiàn)
                  thread.join();
                  // 獲取到的是主線(xiàn)程設(shè)置的值,而不是子線(xiàn)程設(shè)置的
                  System.out.println(threadLocal.get());
                  threadLocal.remove();
                  System.out.println(threadLocal.get());
              }
          }

          運(yùn)行結(jié)果

          null
          sub-thread : World
          main-thread : Hello
          null

          運(yùn)行結(jié)果說(shuō)明了ThreadLocal只能獲取本線(xiàn)程設(shè)置的值,也就是線(xiàn)程封閉。基本上,ThreadLocal對(duì)外提供的方法只有三個(gè)get()、set(T)、remove()。

          原理

          使用方式非常簡(jiǎn)單,所以我們來(lái)看看ThreadLocal的源碼。ThreadLocal內(nèi)部定義了一個(gè)靜態(tài)ThreadLocalMap類(lèi),ThreadLocalMap內(nèi)部又定義了一個(gè)Entry類(lèi),這里只看一些主要的屬性和方法

          public class ThreadLocal<T> {

              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();
              }

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

               public void remove() {
                   ThreadLocalMap m = getMap(Thread.currentThread());
                   if (m != null)
                       m.remove(this);
               }

           // 從這里可以看出ThreadLocalMap對(duì)象是被Thread類(lèi)持有的
              ThreadLocalMap getMap(Thread t) {
                  return t.threadLocals;
              }

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

           // 內(nèi)部類(lèi)ThreadLocalMap
              static class ThreadLocalMap {
                  static class Entry extends WeakReference<ThreadLocal<?>> {
                      Object value;
             // 內(nèi)部類(lèi)Entity,實(shí)際存儲(chǔ)數(shù)據(jù)的地方
             // Entry的key是ThreadLocal對(duì)象,不是當(dāng)前線(xiàn)程ID或者名稱(chēng)
                      Entry(ThreadLocal<?> k, Object v) {
                          super(k);
                          value = v;
                      }
                  }
            // 注意這里維護(hù)的是Entry數(shù)組
                  private Entry[] table;
              }
          }

          根據(jù)上面的源碼,可以大致畫(huà)出ThreadLocal在虛擬機(jī)內(nèi)存中的結(jié)構(gòu)

          實(shí)線(xiàn)箭頭表示強(qiáng)引用,虛線(xiàn)箭頭表示弱引用(關(guān)于對(duì)象的四種引用,可以參考博主之前的博客:Java中四種引用)。需要注意的是:

          • ThreadLocalMap雖然是在ThreadLocal類(lèi)中定義的,但是實(shí)際上被Thread持有。
          • Entry的key是(虛引用的)ThreadLocal對(duì)象,而不是當(dāng)前線(xiàn)程ID或者線(xiàn)程名稱(chēng)。
          • ThreadLocalMap中持有的是Entry數(shù)組,而不是Entry對(duì)象。

          對(duì)于第一點(diǎn),ThreadLocalMap被Thread持有是為了實(shí)現(xiàn)每個(gè)線(xiàn)程都有自己獨(dú)立的ThreadLocalMap對(duì)象,以此為基礎(chǔ),做到線(xiàn)程隔離。第二點(diǎn)和第三點(diǎn)理解,我們先來(lái)想一個(gè)問(wèn)題,如果同一個(gè)線(xiàn)程中定義了多個(gè)ThreadLocal對(duì)象,內(nèi)存結(jié)構(gòu)應(yīng)該是怎樣的?此時(shí)再來(lái)看一下ThreadLocal.set(T)方法:

           public void set(T value) {
             // 獲取當(dāng)前線(xiàn)程對(duì)象
               Thread t = Thread.currentThread();
               // 根據(jù)線(xiàn)程對(duì)象獲取ThreadLocalMap對(duì)象(ThreadLocalMap被Thread持有)
               ThreadLocalMap map = getMap(t);
               // 如果ThreadLocalMap存在,則直接插入;不存在,則新建ThreadLocalMap
               if (map != null)
                   map.set(this, value);
               else
                   createMap(t, value);
           }

          也就是說(shuō),如果程序定義了多個(gè)ThreadLocal,會(huì)共用一個(gè)ThreadLocalMap對(duì)象,所以?xún)?nèi)存結(jié)構(gòu)應(yīng)該是這樣

          這個(gè)內(nèi)存結(jié)構(gòu)圖解釋了第二點(diǎn)和第三點(diǎn)。假設(shè)Entry中key為當(dāng)前線(xiàn)程ID或者名稱(chēng)的話(huà),那么程序中定義多個(gè)ThreadLocal對(duì)象時(shí),Entry數(shù)組中的所有Entry的key都一樣(或者說(shuō)只能存一個(gè)value)。ThreadLocalMap中持有的是Entry數(shù)組,而不是Entry,則是因?yàn)槌绦蚩啥x多個(gè)ThreadLocal對(duì)象,自然需要一個(gè)數(shù)組。

          內(nèi)存泄漏

          ThreadLocal會(huì)發(fā)生內(nèi)存泄漏嗎?

          會(huì)

          仔細(xì)看下ThreadLocal內(nèi)存結(jié)構(gòu)就會(huì)發(fā)現(xiàn),Entry數(shù)組對(duì)象通過(guò)ThreadLocalMap最終被Thread持有,并且是強(qiáng)引用。也就是說(shuō)Entry數(shù)組對(duì)象的生命周期和當(dāng)前線(xiàn)程一樣。即使ThreadLocal對(duì)象被回收了,Entry數(shù)組對(duì)象也不一定被回收,這樣就有可能發(fā)生內(nèi)存泄漏。ThreadLocal在設(shè)計(jì)的時(shí)候就提供了一些補(bǔ)救措施:

          • Entry的key是弱引用的ThreadLocal對(duì)象,很容易被回收,導(dǎo)致key為null(但是value不為null)。所以在調(diào)用get()、set(T)、remove()等方法的時(shí)候,會(huì)自動(dòng)清理key為null的Entity。
          • remove()方法就是用來(lái)清理無(wú)用對(duì)象,防止內(nèi)存泄漏的。所以每次用完ThreadLocal后需要手動(dòng)remove()。

          有些文章認(rèn)為是弱引用導(dǎo)致了內(nèi)存泄漏,其實(shí)是不對(duì)的。假設(shè)把弱引用變成強(qiáng)引用,這樣無(wú)用的對(duì)象key和value都不為null,反而不利于GC,只能通過(guò)remove()方法手動(dòng)清理,或者等待線(xiàn)程結(jié)束生命周期。也就是說(shuō)ThreadLocalMap的生命周期由持有它的線(xiàn)程來(lái)決定,線(xiàn)程如果不進(jìn)入terminated狀態(tài),ThreadLocalMap就不會(huì)被GC回收,這才是ThreadLocal內(nèi)存泄露的原因。

          應(yīng)用場(chǎng)景

          • 維護(hù)JDBC的java.sql.Connection對(duì)象,因?yàn)槊總€(gè)線(xiàn)程都需要保持特定的Connection對(duì)象。
          • Web開(kāi)發(fā)時(shí),有些信息需要從controller傳到service傳到dao,甚至傳到util類(lèi)??雌饋?lái)非常不優(yōu)雅,這時(shí)便可以使用ThreadLocal來(lái)優(yōu)雅的實(shí)現(xiàn)。
          • 包括線(xiàn)程不安全的工具類(lèi),比如Random、SimpleDateFormat等

          與synchronized的關(guān)系

          有些文章拿ThreadLocal和synchronized比較,其實(shí)它們的實(shí)現(xiàn)思想不一樣。

          • synchronized是同一時(shí)間最多只有一個(gè)線(xiàn)程執(zhí)行,所以變量只需要存一份,算是一種時(shí)間換空間的思想
          • ThreadLocal是多個(gè)線(xiàn)程互不影響,所以每個(gè)線(xiàn)程存一份變量,算是一種空間換時(shí)間的思想

          總結(jié)

          ThreadLocal是一種隔離的思想,當(dāng)一個(gè)變量需要進(jìn)行線(xiàn)程隔離時(shí),就可以考慮使用ThreadLocal來(lái)優(yōu)雅的實(shí)現(xiàn)。


          點(diǎn)擊閱讀全前往微服務(wù)電商教程
          瀏覽 73
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  夜夜夜操操操 | 青青青久 | 伊人大香蕉网址 | 国产一级婬乱片 | 影音先锋在线爱爱 |