<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

          共 1521字,需瀏覽 4分鐘

           ·

          2022-01-23 13:56

          引言

          其實(shí)網(wǎng)上有很多關(guān)于ThreadLocal的文章了,有不少文章也已經(jīng)寫非常好了。但是很多同學(xué)反映還有一些部分沒有講解的十分清楚,總覺得有一些疑惑沒有理解的十分清楚。因此本文主要結(jié)合常見的一些疑問、ThreadLocal源碼、應(yīng)用實(shí)例以注意事項(xiàng)來全面而深入地再詳細(xì)講解一遍ThreadLocal。希望大家看完本文后可以徹底掌握ThreadLocal。

          ?

          ThreadLocal是什么?它能干什么

          在闡述ThreadLocal之前,我們先來看下它的設(shè)計(jì)者是怎么描述ThreadLocal的吧。


          看完官方的描述后,結(jié)合自己的理解,ThreadLocal提供了一種對(duì)應(yīng)獨(dú)立線程內(nèi)的數(shù)據(jù)訪問機(jī)制,實(shí)現(xiàn)了變量在線程之間隔離,在線程生命周期內(nèi)獨(dú)立獲取或者設(shè)置的能力。如果我們想在線程內(nèi)傳遞參數(shù)但是有不想作為方法參數(shù)的時(shí)候,ThreadLocal就可以上用場(chǎng)了。不過值得注意的是ThreadLocal并不會(huì)解決變量共享問題。實(shí)際上從ThreadLocal的名稱上面來看,線程本地變量也已經(jīng)大致說明了它的作用,所以變量的命名還是非常重要的,要做到顧名思義。如果覺得還不是很理解,沒關(guān)系,我們可以通過以下的場(chǎng)景再加深下理解。


          假如有以下的場(chǎng)景,假設(shè)只有一個(gè)數(shù)據(jù)庫連接,客戶端1、2、3都需要獲取數(shù)據(jù)庫連接來進(jìn)行具體的數(shù)據(jù)庫操作,但是同一時(shí)間點(diǎn)只能有一個(gè)線程獲取連接,其他線程只能等待。因此就會(huì)出現(xiàn)數(shù)據(jù)庫訪問效率不高的問題。

          那我們有沒有什么辦法能夠避免線程等待的情況呢?上述問題的根本原因是數(shù)據(jù)庫連接是共享變量,同時(shí)只能有一個(gè)線程可以進(jìn)行操作。那如果三個(gè)線程都有自己的數(shù)據(jù)庫連接,互相隔離,那不就不會(huì)出現(xiàn)等待的問題了嘛。那么此時(shí)我可以使用ThreadLocal實(shí)現(xiàn)在不同線程中的變量隔離??梢钥闯鰜?,ThreadLocal是一種空間換取時(shí)間的做法。


          ?

          ThreadLocal實(shí)現(xiàn)線程隔離的秘密

          從上文中,我們了解到ThreadLocal可以實(shí)現(xiàn)變量訪問的線程級(jí)別的隔離。那么它是到底如何實(shí)現(xiàn)的呢?這還需要結(jié)合Thread以及ThreadLocal的源碼來分析才能揭開ThreadLocal實(shí)現(xiàn)線程隔離的神秘面紗。

          public?class?Thread?implements?Runnable?{
          ????...
          ????/*?ThreadLocal?values?pertaining?to?this?thread.?This?map?is?maintained
          ?????*?by?the?ThreadLocal?class.?*/

          ????ThreadLocal.ThreadLocalMap?threadLocals?=?null;
          ????...
          ????
          }


          在Thread源碼中我們發(fā)現(xiàn),它有一個(gè)threadLocals變量,它的類型是ThreadLocal中的內(nèi)部類ThreadLocalMap。我們?cè)倏聪耇hreadLocalMap的定義是怎樣的。從源碼中我們可以看出來,ThreadLocalMap實(shí)際上就是Entry數(shù)組,這個(gè)Entry對(duì)應(yīng)的key實(shí)際就是ThreadLocal的實(shí)例,value就是實(shí)際的變量值。

          public?class?ThreadLocal<T>?{
          ??...
          ????
          ???static?class?ThreadLocalMap?{
          ?????
          ??????static?class?Entry?extends?WeakReference<ThreadLocal>?{
          ????????????/**?The?value?associated?with?this?ThreadLocal.?*/
          ????????????Object?value;

          ????????????Entry(ThreadLocal?k,?Object?v)?{
          ????????????????super(k);
          ????????????????value?=?v;
          ????????????}
          ????????}
          ???????...
          ???????//底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組
          ???????private?Entry[]?table;
          ???????...
          ?????
          ???}
          ??...
          ??
          }


          通過查看上述的源碼,如果還不太好理解的話,我們?cè)俳Y(jié)合下現(xiàn)實(shí)中的例子來理解。大家都有支付寶賬戶,我們通過它來管理著我們的銀行卡、余額、花唄這些金融服務(wù)。

          我們以支付寶以及支付寶賬戶進(jìn)行類比,假設(shè)ThreadLocal就是支付寶,每個(gè)支付寶賬戶實(shí)際就是單獨(dú)的線程,而賬戶中的余額屬性就相當(dāng)于Thread的私有屬性ThreadLocalMap。我們?cè)谌粘I钪校M(jìn)行賬戶余額的充值或者消費(fèi),并不是直接通過賬戶進(jìn)行操作的,而是借助于支付寶進(jìn)行維護(hù)的。這就相當(dāng)于每個(gè)線程對(duì)ThreadLocalMap進(jìn)行操作的時(shí)候也不是直接操作的,而是借助于ThreadLocal來操作。


          那么Thread到底是怎么借助ThreadLocal進(jìn)行私有屬性管理的呢?還是需要進(jìn)一步查看Thread進(jìn)行set以及get操作的源碼。從以下的ThreadLocal的源碼中我們可以看出,在進(jìn)行操作之前,需要獲取當(dāng)前的執(zhí)行操作的線程,再根據(jù)線程或者線程中私有的ThreadLocalMap屬性來進(jìn)行操作。


          在進(jìn)行數(shù)據(jù)獲取的時(shí)候,也是按照同樣的流程,先獲取當(dāng)前的線程,再獲取線程中對(duì)應(yīng)的ThreadLocalMap屬性來進(jìn)行后續(xù)的值的獲取。


          經(jīng)過上述的源碼的分析,我們可以得出這樣的結(jié)論,ThreadLocal之所以可以實(shí)現(xiàn)變量的線程隔離訪問,實(shí)際上就是借助于Thread中的ThreadLocalMap屬性來進(jìn)行操作。由于都是操作線程本身的屬性,因此并不會(huì)影響其他線程中的變量值,因此可以實(shí)現(xiàn)線程級(jí)別的數(shù)據(jù)修改隔離。


          ?

          為什么會(huì)出現(xiàn)OOM?

          內(nèi)存泄漏演示

          我們都知道,ThreadLocal如果使用不當(dāng)?shù)脑挄?huì)出現(xiàn)內(nèi)存泄漏的問題,那么我們就通過下面的這段代碼來分析下,內(nèi)存泄漏的原因到底是什么。


          /**
          ?*?@author?mufeng
          ?*?@description?測(cè)試ThreadLocal內(nèi)存溢出
          ?*?@date?2022/1/16?19:01
          ?*?@since
          ?*/

          public?class?ThreadLocalOOM?{

          ????/**
          ?????*?測(cè)試線程池
          ?????*/

          ????private?static?Executor?threadPool?=?new?ThreadPoolExecutor(3,?3,?40,
          ????????????TimeUnit.SECONDS,?new?LinkedBlockingDeque<>());


          ????static?class?Info?{
          ????????private?byte[]?info?=?new?byte[10?*?1024?*?1024];
          ????}

          ????private??static?ThreadLocal?infoThreadLocal?=?new?ThreadLocal<>();

          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????for?(int?i?=?0;?i?10
          ;?i++)?{
          ????????????threadPool.execute(()?->?{
          ????????????????infoThreadLocal.set(new?Info());
          ????????????????System.out.println("Thread?started:"?+?Thread.currentThread().getName());
          ????????????});
          ????????????Thread.sleep(100);
          ????????}

          ????}
          }


          手動(dòng)進(jìn)行GC之后,我們可以發(fā)現(xiàn)堆中仍然有超過30M的堆內(nèi)存占用,如上面的代碼,在線程池中活躍的線程會(huì)有三個(gè),對(duì)應(yīng)的value為10M,說明在線程還存活的情況下,對(duì)應(yīng)的value并沒有被回收,因此存在內(nèi)存泄漏的情況,如果存在大量線程的情況,就會(huì)出現(xiàn)OOM。

          當(dāng)我們修改代碼在線程中進(jìn)行remove操作,手動(dòng)GC之后我們發(fā)現(xiàn)堆內(nèi)存趨近于0了,之前沒有被回收的對(duì)象已經(jīng)被回收了。


          內(nèi)存泄漏問題分析

          以上是對(duì)于ThreadLocal發(fā)生內(nèi)存泄漏問題的演示,那么再來仔細(xì)分析下背后的原因是什么。ThreadLocal中實(shí)際存儲(chǔ)數(shù)據(jù)的是ThreadLocalMap,實(shí)際上Map對(duì)應(yīng)的key是一個(gè)虛引用,在GC的時(shí)候可以被回收掉,但是問題就在于key所對(duì)應(yīng)的value,它是強(qiáng)引用,只要線程存活,那么這條引用鏈就會(huì)一致存在,如果出現(xiàn)大量線程的時(shí)候就會(huì)有OOM的風(fēng)險(xiǎn)。

          所以在使用ThreadLocal的時(shí)候一定記得要顯式的調(diào)用remove方法進(jìn)行清理,防止內(nèi)存泄漏。

          父子線程的參數(shù)傳遞

          到這里,我相信大家對(duì)于ThreadLocal的原理有了比較深入的理解了。結(jié)合上文中的ThreadLocal代碼,不知道大家有沒有思考過一個(gè)問題,我們?cè)谑褂肨hreadLocal的時(shí)候都是在同一個(gè)線程內(nèi)進(jìn)行了set以及get操作,那么如果set操作與get操作在父子線程中是否還可以正常地獲取呢?帶著這樣的疑問,我們來看下如下的代碼。

          /**
          ?*?@author?mufeng
          ?*?@description?父子線程參數(shù)傳遞
          ?*?@date?2022/1/16?9:54
          ?*?@since
          ?*/

          public?class?InheritableThreadLocalMain?{

          ????private?static?final?ThreadLocal?count?=?new?ThreadLocal<>();

          ????public?static?void?main(String[]?args)?{

          ????????count.set("父子線程參數(shù)傳遞?。?!");
          ????????System.out.println(Thread.currentThread().getName()?+?":"?+?count.get());

          ????????new?Thread(()?->?{
          ????????????System.out.println(Thread.currentThread().getName()?+?":"?+?count.get());
          ????????}).start();

          ????}

          }



          與之前代碼有所不同,ThreadLocal的設(shè)值是在main線程中進(jìn)行的,但是獲取操作實(shí)際是在主線程下的子線程中進(jìn)行的,大家可以分析一下運(yùn)行結(jié)果是怎么樣的。



          看到這個(gè)運(yùn)行結(jié)果,不知道大家分析地對(duì)不對(duì)呢。實(shí)際上如果理解了上文的核心的話,這個(gè)問題應(yīng)該很好分析的。ThreadLocal獲取數(shù)據(jù)的時(shí)候,首先是需要獲取當(dāng)前的線程的,根據(jù)線程獲取實(shí)際存儲(chǔ)數(shù)據(jù)的ThreadLocalMap,上文代碼中設(shè)置和獲取在父子線程中進(jìn)行,那肯定是獲取不到設(shè)置的數(shù)據(jù)的。但是在現(xiàn)實(shí)的項(xiàng)目開發(fā)中,我們會(huì)經(jīng)常遇到需要將父線程的變量值傳遞給子線程進(jìn)行處理,那么應(yīng)該要怎么來實(shí)現(xiàn)呢?這個(gè)時(shí)候InheritableThreadLocal就派上用場(chǎng)了。


          /**
          ?*?@author?mufeng
          ?*?@description?父子線程參數(shù)傳遞
          ?*?@date?2022/1/16?9:54
          ?*?@since
          ?*/

          public?class?InheritableThreadLocalMain?{

          ????private?static?final?ThreadLocal?count?=?new?InheritableThreadLocal<>();

          ????public?static?void?main(String[]?args)?{

          ????????count.set("父子線程參數(shù)傳遞?。?!");
          ????????System.out.println(Thread.currentThread().getName()?+?":"?+?count.get());

          ????????new?Thread(()?->?{
          ????????????System.out.println(Thread.currentThread().getName()?+?":"?+?count.get());
          ????????}).start();

          ????}

          }




          那么InheritableThreadLocal到底是如何實(shí)現(xiàn)父子線程的參數(shù)傳遞的呢?我們還是看看源碼中的實(shí)現(xiàn)原理。實(shí)際上在Thread源碼中,除了有Threadlocal私有屬性還有InheritableThreadLocal私有屬性。


          public?class?Thread?implements?Runnable?{
          ????
          ?????/*?ThreadLocal?values?pertaining?to?this?thread.?This?map?is?maintained
          ?????*?by?the?ThreadLocal?class.?*/

          ????ThreadLocal.ThreadLocalMap?threadLocals?=?null;

          ????/*
          ?????*?InheritableThreadLocal?values?pertaining?to?this?thread.?This?map?is
          ?????*?maintained?by?the?InheritableThreadLocal?class.
          ?????*/

          ????ThreadLocal.ThreadLocalMap?inheritableThreadLocals?=?null;
          ...
          ????public?Thread(Runnable?target)?{
          ????????init(null,?target,?"Thread-"?+?nextThreadNum(),?0);
          ????}
          ????
          ????private?void?init(ThreadGroup?g,?Runnable?target,?String?name,
          ??????????????????????long?stackSize)
          ?
          {
          ????????init(g,?target,?name,?stackSize,?null,?true);
          ????}
          ????
          ????private?void?init(ThreadGroup?g,?Runnable?target,?String?name,
          ??????????????????????long?stackSize,?AccessControlContext?acc,
          ??????????????????????boolean?inheritThreadLocals)
          ?
          {
          ????????...
          ????????//關(guān)鍵
          ?????????if?(inheritThreadLocals?&&?parent.inheritableThreadLocals?!=?null)
          ????????????this.inheritableThreadLocals?=
          ????????????????ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);?
          ????????...????
          ????????
          ????}
          ????...
          ????
          }

          實(shí)際在進(jìn)行子線程創(chuàng)建的時(shí)候,在線程初始化過程中,判斷了父線程中的inheritableThreadLocals屬性是否為空,如果不為空的話需要進(jìn)行值的復(fù)制,這樣便實(shí)現(xiàn)了父子線程的值傳遞。

          ?

          總結(jié)

          本文主要對(duì)ThreadLocal進(jìn)行了相對(duì)全面的分析,從它的使用場(chǎng)景、原理以及源碼分析、產(chǎn)生OOM的原因以及一些使用上的注意,相信通過本文的學(xué)習(xí),大家對(duì)于ThreadLocal會(huì)有更加深刻的理解。


          有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

          歡迎大家關(guān)注Java之道公眾號(hào)


          好文章,我在看??

          瀏覽 91
          點(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>
                  精品七区| 操逼av在线 | 无码窝在线观看 | 国产精品久久久久久久牛牛 | 淫秽三级片中文字幕在线免费观看 |