中高級階段開發者出去面試,應該躲不開ThreadLocal相關問題,本文就常見問題作出一些解答,歡迎留言探討。java
ThreadLocal爲java併發提供了一個新的思路, 它用來存儲Thread的局部變量, 從而達到各個Thread之間的隔離運行。它被普遍應用於框架之間的用戶資源隔離、事務隔離等。面試
可是用很差會致使內存泄漏, 本文重點用於對它的使用過程的疑難解答, 相信仔細閱讀完後的朋友能夠爲所欲爲的安全使用它。spring
ThreadLocal操做不當會引起內存泄露,最主要的緣由在於它的內部類ThreadLocalMap中的Entry的設計。數據庫
Entry繼承了WeakReference<ThreadLocal<?>>
,即Entry的key是弱引用,因此key'會在垃圾回收的時候被回收掉, 而key對應的value則不會被回收, 這樣會致使一種現象:key爲null,value有值。安全
key爲空的話value是無效數據,長此以往,value累加就會致使內存泄漏。多線程
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ... }
每次使用完ThreadLocal都調用它的remove()方法清除數據。由於它的remove方法會主動將當前的key和value(Entry)進行清除。併發
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,它調用的是WeakReference中的方法:this.referent = null框架
expungeStaleEntry(i)用於清除Entry對應的value, 這個後面會詳細講。分佈式
ThreadLocal的設計者也意識到了這一點(內存泄漏), 他們在一些方法中埋了對key=null的value擦除操做。性能
這裏拿ThreadLocal提供的get()方法舉例,它調用了ThreadLocalMap#getEntry()方法,對key進行了校驗和對null key進行擦除。
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, 則會調用getEntryAfterMiss()方法,在這個方法中,若是k == null , 則調用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)方法完成了對key=null 的key所對應的value進行賦空, 釋放了空間避免內存泄漏。
同時它遍歷下一個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; // 遍歷下一個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()方法最終也是調用該方法(expungeStaleEntry), 調用路徑: set(T value)->map.set(this, value)->rehash()->expungeStaleEntries()
remove方法remove()->ThreadLocalMap.remove(this)->expungeStaleEntry(i)
這樣作, 也只能說盡量避免內存泄漏, 但並不會徹底解決內存泄漏這個問題。好比極端狀況下咱們只建立ThreadLocal但不調用set、get、remove方法等。因此最能解決問題的辦法就是用完ThreadLocal後手動調用remove().
這裏主要是強化一下手動remove的思想和必要性,設計思想與鏈接池相似。
包裝其父類remove方法爲靜態方法,若是是spring項目, 能夠藉助於bean的聲明週期, 在攔截器的afterCompletion階段進行調用。
這個問題就比較有深度了,是你談薪的小小資本。
若是key設置爲強引用, 當threadLocal實例釋放後, threadLocal=null, 可是threadLocal會有強引用指向threadLocalMap,threadLocalMap.Entry又強引用threadLocal, 這樣會致使threadLocal不能正常被GC回收。
弱引用雖然會引發內存泄漏, 可是也有set、get、remove方法操做對null key進行擦除的補救措施, 方案上略勝一籌。
一併考察了你的gc基礎。
事實上,當currentThread執行結束後, threadLocalMap變得不可達從而被回收,Entry等也就都被回收了,但這個環境就要求不對Thread進行復用,可是咱們項目中常常會複用線程來提升性能, 因此currentThread通常不會處於終止狀態。
ThreadLocal的概念。
Thread和ThreadLocal是綁定的, ThreadLocal依賴於Thread去執行, Thread將須要隔離的數據存放到ThreadLocal(準確的講是ThreadLocalMap)中, 來實現多線程處理。
加分項來了。
ThreadLocal天生爲解決相同變量的訪問衝突問題, 因此這個對於spring的默認單例bean的多線程訪問是一個完美的解決方案。spring也確實是用了ThreadLocal來處理多線程下相同變量併發的線程安全問題。
要想實現jdbc事務, 就必須是在同一個鏈接對象中操做, 多個鏈接下事務就會不可控, 須要藉助分佈式事務完成。那spring 如何保證數據庫事務在同一個鏈接下執行的呢?
DataSourceTransactionManager 是spring的數據源事務管理器, 它會在你調用getConnection()的時候從數據庫鏈接池中獲取一個connection, 而後將其與ThreadLocal綁定, 事務完成後解除綁定。這樣就保證了事務在同一鏈接下完成。
概要源碼:
1.事務開始階段:org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin->TransactionSynchronizationManager#bindResource->org.springframework.transaction.support.TransactionSynchronizationManager#bindResource
2.事務結束階段:
org.springframework.jdbc.datasource.DataSourceTransactionManager#doCleanupAfterCompletion->TransactionSynchronizationManager#unbindResource->org.springframework.transaction.support.TransactionSynchronizationManager#unbindResource->TransactionSynchronizationManager#doUnbindResource
Java知音10月基礎篇