threadlocal内存泄露_ThreadLocal 简介
本文轉載于SegmentFault社區
作者:莫小點還有救
1. ThreadLocal簡介
通常情況下,我們創建的變量是可以被任何一個線程訪問并修改的。如果想實現每一個線程都有自己的專屬本地變量該如何解決呢?JDK中提供的ThreadLocal類正是為了解決這樣的問題。ThreadLocal類主要解決的就是讓每個線程綁定自己的值,可以將ThreadLocal類形象的比喻成存放數據的盒子,盒子中可以存儲每個線程的私有數據。
如果你創建了一個ThreadLocal變量,那么訪問這個變量的每個線程都會有這個變量的本地副本,這也是ThreadLocal變量名的由來。他們可以使用 get()?和 set()?方法來獲取默認值或將其值更改為當前線程所存的副本的值,從而避免了線程安全問題。
再舉個簡單的例子:
比如有兩個人去寶屋收集寶物,這兩個共用一個袋子的話肯定會產生爭執,但是給他們兩個人每個人分配一個袋子的話就不會出現這樣的問題。如果把這兩個人比作線程的話,那么ThreadLocal就是用來避免這兩個線程競爭的。
2. ThreadLocal示例
相信看了上面的解釋,大家已經搞懂 ThreadLocal 類是個什么東西了。
import java.text.SimpleDateFormat;import java.util.Random;public class ThreadLocalExample implements Runnable{ // SimpleDateFormat 不是線程安全的,所以每個線程都要有自己獨立的副本 private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm")); public static void main(String[] args) throws InterruptedException { ThreadLocalExample obj = new ThreadLocalExample(); for(int i=0 ; i<10; i++){ Thread t = new Thread(obj, ""+i); Thread.sleep(new Random().nextInt(1000)); t.start(); } } @Override public void run() { System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern()); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } //formatter pattern is changed here by thread, but it won't reflect to other threads formatter.set(new SimpleDateFormat()); System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern()); }}Output:
Thread Name= 0 default Formatter = yyyyMMdd HHmmThread Name= 0 formatter = yy-M-d ah:mmThread Name= 1 default Formatter = yyyyMMdd HHmmThread Name= 2 default Formatter = yyyyMMdd HHmmThread Name= 1 formatter = yy-M-d ah:mmThread Name= 3 default Formatter = yyyyMMdd HHmmThread Name= 2 formatter = yy-M-d ah:mmThread Name= 4 default Formatter = yyyyMMdd HHmmThread Name= 3 formatter = yy-M-d ah:mmThread Name= 4 formatter = yy-M-d ah:mmThread Name= 5 default Formatter = yyyyMMdd HHmmThread Name= 5 formatter = yy-M-d ah:mmThread Name= 6 default Formatter = yyyyMMdd HHmmThread Name= 6 formatter = yy-M-d ah:mmThread Name= 7 default Formatter = yyyyMMdd HHmmThread Name= 7 formatter = yy-M-d ah:mmThread Name= 8 default Formatter = yyyyMMdd HHmmThread Name= 9 default Formatter = yyyyMMdd HHmmThread Name= 8 formatter = yy-M-d ah:mmThread Name= 9 formatter = yy-M-d ah:mm從輸出中可以看出,Thread-0已經改變了formatter的值,但仍然是thread-2默認格式化程序與初始化值相同,其他線程也一樣。
上面有一段代碼用到了創建 ThreadLocal 變量的那段代碼用到了 Java8 的知識,它等于下面這段代碼,如果你寫了下面這段代碼的話,IDEA會提示你轉換為Java8的格式(IDEA真的不錯!)。因為ThreadLocal類在Java 8中擴展,使用一個新的方法withInitial(),將Supplier功能接口作為參數。
private static final ThreadLocal formatter = new ThreadLocal(){ @Override protected SimpleDateFormat initialValue(){ return new SimpleDateFormat("yyyyMMdd HHmm"); } };3. ThreadLocal原理
從 Thread類源代碼入手。
public class Thread implements Runnable { ......//與此線程有關的ThreadLocal值。由ThreadLocal類維護ThreadLocal.ThreadLocalMap threadLocals = null;//與此線程有關的InheritableThreadLocal值。由InheritableThreadLocal類維護ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ......}從上面Thread類 源代碼可以看出Thread 類中有一個 threadLocals 和 一個 inheritableThreadLocals 變量,它們都是 ThreadLocalMap 類型的變量,我們可以把 ThreadLocalMap 理解為ThreadLocal 類實現的定制化的 HashMap。默認情況下這兩個變量都是null,只有當前線程調用 ThreadLocal 類的 set或get方法時才創建它們,實際上調用這兩個方法的時候,我們調用的是ThreadLocalMap類對應的 get()、set()方法。
ThreadLocal類的set()方法
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }通過上面這些內容,我們足以通過猜測得出結論:最終的變量是放在了當前線程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解為只是ThreadLocalMap的封裝,傳遞了變量值。ThrealLocal 類中可以通過Thread.currentThread()獲取到當前線程對象后,直接通過getMap(Thread t)可以訪問到該線程的ThreadLocalMap對象。
每個Thread中都具備一個ThreadLocalMap,而ThreadLocalMap可以存儲以ThreadLocal為key ,Object 對象為 value的鍵值對。
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) { ......}比如我們在同一個線程中聲明了兩個 ThreadLocal 對象的話,會使用 Thread內部都是使用僅有那個ThreadLocalMap 存放數據的,ThreadLocalMap的 key 就是 ThreadLocal對象,value 就是 ThreadLocal 對象調用set方法設置的值。
ThreadLocalMap是ThreadLocal的靜態內部類。
4. ThreadLocal 內存泄露問題
ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,而 value 是強引用。所以,如果 ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候,key 會被清理掉,而 value 不會被清理掉。這樣一來,ThreadLocalMap 中就會出現key為null的Entry。假如我們不做任何措施的話,value 永遠無法被GC 回收,這個時候就可能會產生內存泄露。ThreadLocalMap實現中已經考慮了這種情況,在調用 set()、get()、remove()?方法的時候,會清理掉 key 為 null 的記錄。使用完 ThreadLocal方法后 最好手動調用remove()方法
static class Entry extends WeakReference<ThreadLocal>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal> k, Object v) { super(k); value = v; } }弱引用介紹:
如果一個對象只具有弱引用,那就類似于可有可無的生活用品。弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它 所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程, 因此不一定會很快發現那些只具有弱引用的對象。
弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
-?END -
總結
以上是生活随笔為你收集整理的threadlocal内存泄露_ThreadLocal 简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: maven 程序包不存在_有人说 Mav
- 下一篇: python字符串替换空格_python