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

          來(lái)探討一下最近面試問(wèn)的ThreadLocal問(wèn)題

          共 317字,需瀏覽 1分鐘

           ·

          2019-11-17 23:21

          中高級(jí)階段開(kāi)發(fā)者出去面試,應(yīng)該躲不開(kāi)ThreadLocal相關(guān)問(wèn)題,本文就常見(jiàn)問(wèn)題做出一些解答,歡迎留言探討。

          ThreadLocal為java并發(fā)提供了一個(gè)新的思路, 它用來(lái)存儲(chǔ)Thread的局部變量, 從而達(dá)到各個(gè)Thread之間的隔離運(yùn)行。它被廣泛應(yīng)用于框架之間的用戶資源隔離、事務(wù)隔離等。


          但是用不好會(huì)導(dǎo)致內(nèi)存泄漏, 本文重點(diǎn)用于對(duì)它的使用過(guò)程的疑難解答, 相信仔細(xì)閱讀完后的朋友可以隨心所欲的安全使用它。

          內(nèi)存泄漏原因探索

          ThreadLocal操作不當(dāng)會(huì)引發(fā)內(nèi)存泄露,最主要的原因在于它的內(nèi)部類ThreadLocalMap中的Entry的設(shè)計(jì)。

          Entry繼承了WeakReference>,即Entry的key是弱引用,所以key'會(huì)在垃圾回收的時(shí)候被回收掉, 而key對(duì)應(yīng)的value則不會(huì)被回收, 這樣會(huì)導(dǎo)致一種現(xiàn)象:key為null,value有值。

          key為空的話value是無(wú)效數(shù)據(jù),久而久之,value累加就會(huì)導(dǎo)致內(nèi)存泄漏。

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

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

          怎么解決這個(gè)內(nèi)存泄漏問(wèn)題

          每次使用完ThreadLocal都調(diào)用它的remove()方法清除數(shù)據(jù)。因?yàn)樗膔emove方法會(huì)主動(dòng)將當(dāng)前的key和value(Entry)進(jìn)行清除。

          private?void?remove(ThreadLocal?key)?{
          ????Entry[]?tab?=?table;
          ????int?len?=?tab.length;
          ????int?i?=?key.threadLocalHashCode?&?(len-1);
          ????for?(Entry?e?=?tab[i];
          ?????????e?!=?null;
          ?????????e?=?tab[i?=?nextIndex(i,?len)])?{
          ????????if?(e.get()?==?key)?{
          ????????????e.clear();?//?清除key
          ????????????expungeStaleEntry(i);??//?清除value
          ????????????return;
          ????????}
          ????}
          }

          e.clear()用于清除Entry的key,它調(diào)用的是WeakReference中的方法:this.referent = null

          expungeStaleEntry(i)用于清除Entry對(duì)應(yīng)的value, 這個(gè)后面會(huì)詳細(xì)講。

          JDK開(kāi)發(fā)者是如何避免內(nèi)存泄漏的

          ThreadLocal的設(shè)計(jì)者也意識(shí)到了這一點(diǎn)(內(nèi)存泄漏), 他們?cè)谝恍┓椒ㄖ新窳藢?duì)key=null的value擦除操作。

          這里拿ThreadLocal提供的get()方法舉例,它調(diào)用了ThreadLocalMap#getEntry()方法,對(duì)key進(jìn)行了校驗(yàn)和對(duì)null key進(jìn)行擦除。

          private?Entry?getEntry(ThreadLocal?key)?{
          ????//?拿到索引位置
          ????int?i?=?key.threadLocalHashCode?&?(table.length?-?1);
          ????Entry?e?=?table[i];
          ????if?(e?!=?null?&&?e.get()?==?key)
          ????????return?e;
          ????else
          ????????return?getEntryAfterMiss(key,?i,?e);
          }

          如果key為null, 則會(huì)調(diào)用getEntryAfterMiss()方法,在這個(gè)方法中,如果k == null , 則調(diào)用expungeStaleEntry(i);方法。

          private?Entry?getEntryAfterMiss(ThreadLocal?key,?int?i,?Entry?e)?{
          ????Entry[]?tab?=?table;
          ????int?len?=?tab.length;

          ????while?(e?!=?null)?{
          ????????ThreadLocal?k?=?e.get();
          ????????if?(k?==?key)
          ????????????return?e;
          ????????if?(k?==?null)
          ????????????expungeStaleEntry(i);
          ????????else
          ????????????i?=?nextIndex(i,?len);
          ????????e?=?tab[i];
          ????}
          ????return?null;
          ????}

          expungeStaleEntry(i)方法完成了對(duì)key=null 的key所對(duì)應(yīng)的value進(jìn)行賦空, 釋放了空間避免內(nèi)存泄漏。

          同時(shí)它遍歷下一個(gè)key為空的entry, 并將value賦值為null, 等待下次GC釋放掉其空間。

          private?int?expungeStaleEntry(int?staleSlot)?{
          ????Entry[]?tab?=?table;
          ????int?len?=?tab.length;

          ????//?expunge?entry?at?staleSlot
          ????tab[staleSlot].value?=?null;
          ????tab[staleSlot]?=?null;
          ????size--;

          ????//?Rehash?until?we?encounter?null
          ????Entry?e;
          ????int?i;
          ????//?遍歷下一個(gè)key為空的entry,?并將value指向null
          ????for?(i?=?nextIndex(staleSlot,?len);
          ?????????(e?=?tab[i])?!=?null;
          ?????????i?=?nextIndex(i,?len))?{
          ????????ThreadLocal?k?=?e.get();
          ????????if?(k?==?null)?{
          ????????????e.value?=?null;
          ????????????tab[i]?=?null;
          ????????????size--;
          ????????}?else?{
          ????????????int?h?=?k.threadLocalHashCode?&?(len?-?1);
          ????????????if?(h?!=?i)?{
          ????????????????tab[i]?=?null;

          ????????????????//?Unlike?Knuth?6.4?Algorithm?R,?we?must?scan?until
          ????????????????//?null?because?multiple?entries?could?have?been?stale.
          ????????????????while?(tab[h]?!=?null)
          ????????????????????h?=?nextIndex(h,?len);
          ????????????????tab[h]?=?e;
          ????????????}
          ????????}
          ????}
          ????return?i;
          }

          同理, set()方法最終也是調(diào)用該方法(expungeStaleEntry), 調(diào)用路徑: set(T value)->map.set(this, value)->rehash()->expungeStaleEntries()

          remove方法remove()->ThreadLocalMap.remove(this)->expungeStaleEntry(i)

          這樣做, 也只能說(shuō)盡可能避免內(nèi)存泄漏, 但并不會(huì)完全解決內(nèi)存泄漏這個(gè)問(wèn)題。比如極端情況下我們只創(chuàng)建ThreadLocal但不調(diào)用set、get、remove方法等。所以最能解決問(wèn)題的辦法就是用完ThreadLocal后手動(dòng)調(diào)用remove().

          手動(dòng)釋放ThreadLocal遺留存儲(chǔ)?你怎么去設(shè)計(jì)/實(shí)現(xiàn)?

          這里主要是強(qiáng)化一下手動(dòng)remove的思想和必要性,設(shè)計(jì)思想與連接池類似。

          包裝其父類remove方法為靜態(tài)方法,如果是spring項(xiàng)目, 可以借助于bean的聲明周期, 在攔截器的afterCompletion階段進(jìn)行調(diào)用。

          弱引用導(dǎo)致內(nèi)存泄漏,那為什么key不設(shè)置為強(qiáng)引用

          這個(gè)問(wèn)題就比較有深度了,是你談薪的小小資本。

          如果key設(shè)置為強(qiáng)引用, 當(dāng)threadLocal實(shí)例釋放后, threadLocal=null, 但是threadLocal會(huì)有強(qiáng)引用指向threadLocalMap,threadLocalMap.Entry又強(qiáng)引用threadLocal, 這樣會(huì)導(dǎo)致threadLocal不能正常被GC回收。

          弱引用雖然會(huì)引起內(nèi)存泄漏, 但是也有set、get、remove方法操作對(duì)null key進(jìn)行擦除的補(bǔ)救措施, 方案上略勝一籌。

          線程執(zhí)行結(jié)束后會(huì)不會(huì)自動(dòng)清空Entry的value

          一并考察了你的gc基礎(chǔ)。

          事實(shí)上,當(dāng)currentThread執(zhí)行結(jié)束后, threadLocalMap變得不可達(dá)從而被回收,Entry等也就都被回收了,但這個(gè)環(huán)境就要求不對(duì)Thread進(jìn)行復(fù)用,但是我們項(xiàng)目中經(jīng)常會(huì)復(fù)用線程來(lái)提高性能, 所以currentThread一般不會(huì)處于終止?fàn)顟B(tài)。

          Thread和ThreadLocal有什么聯(lián)系呢

          ThreadLocal的概念。

          Thread和ThreadLocal是綁定的, ThreadLocal依賴于Thread去執(zhí)行, Thread將需要隔離的數(shù)據(jù)存放到ThreadLocal(準(zhǔn)確的講是ThreadLocalMap)中, 來(lái)實(shí)現(xiàn)多線程處理。

          相關(guān)問(wèn)題擴(kuò)展

          加分項(xiàng)來(lái)了。

          spring如何處理bean多線程下的并發(fā)問(wèn)題

          ThreadLocal天生為解決相同變量的訪問(wèn)沖突問(wèn)題, 所以這個(gè)對(duì)于spring的默認(rèn)單例bean的多線程訪問(wèn)是一個(gè)完美的解決方案。spring也確實(shí)是用了ThreadLocal來(lái)處理多線程下相同變量并發(fā)的線程安全問(wèn)題。

          spring 如何保證數(shù)據(jù)庫(kù)事務(wù)在同一個(gè)連接下執(zhí)行的

          要想實(shí)現(xiàn)jdbc事務(wù), 就必須是在同一個(gè)連接對(duì)象中操作, 多個(gè)連接下事務(wù)就會(huì)不可控, 需要借助分布式事務(wù)完成。那spring 如何保證數(shù)據(jù)庫(kù)事務(wù)在同一個(gè)連接下執(zhí)行的呢?

          DataSourceTransactionManager 是spring的數(shù)據(jù)源事務(wù)管理器, 它會(huì)在你調(diào)用getConnection()的時(shí)候從數(shù)據(jù)庫(kù)連接池中獲取一個(gè)connection, 然后將其與ThreadLocal綁定, 事務(wù)完成后解除綁定。這樣就保證了事務(wù)在同一連接下完成。

          概要源碼:

          1.事務(wù)開(kāi)始階段:org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin->TransactionSynchronizationManager#bindResource->org.springframework.transaction.support.TransactionSynchronizationManager#bindResource

          964225b398e2f3f92bbb11e809c9a679.webp

          2.事務(wù)結(jié)束階段:

          org.springframework.jdbc.datasource.DataSourceTransactionManager#doCleanupAfterCompletion->TransactionSynchronizationManager#unbindResource->org.springframework.transaction.support.TransactionSynchronizationManager#unbindResource->TransactionSynchronizationManager#doUnbindResource

          b1fb878b9a74d1ea08d2ea258021f142.webp



          推薦閱讀:


          7e73b2e7e495e1cb65f665ca38fd54f0.webp喜歡我可以給我設(shè)為星標(biāo)哦7e73b2e7e495e1cb65f665ca38fd54f0.webp

          fbac91978329eb80fb6703c3a0a27344.webp

          好文章,我?在看?

          dc0956e75ee164f884122379cb2de619.webp
          瀏覽 65
          點(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>
                  肏比视频 | 成人视频在线观看视频在线观看黄色 | 国产日逼视频 | 欧美日韩国产免费观看 | 三级影院麻 |