日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一文搞懂ThreadLocal及相关的内存泄露问题

發布時間:2023/12/3 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文搞懂ThreadLocal及相关的内存泄露问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

首先,看一張整體的結構圖,來幫助理解

什么是ThreadLocal

ThreadLocal用于創建線程局部變量,如果創建一個ThreadLocal變量,那么訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內存中的變量,從而規避了線程安全問題

ThreadLocal的簡單使用

package test;public class ThreadLocalTest {static ThreadLocal<String> threadLocal= new ThreadLocal<>();public static void main(String[] args) {Thread t1 = new Thread(()->{//設置線程1的本地變量的值threadLocal.set("線程1");System.out.println(threadLocal.get());threadLocal.remove();System.out.println("after remove : " + threadLocal.get());});Thread t2 = new Thread(()->{//設置線程2的本地變量threadLocal.set("線程2");System.out.println(threadLocal.get());threadLocal.remove();System.out.println("after remove : " + threadLocal.get());});t1.start();t2.start();} }

輸出結果:

由此可知,雖然threadLocal為成員變量,即線程共享,但使用threadLocal在不用線程進行賦值、獲取等操作時,不同線程的操作互不相擾。
那么ThreadLocal是如何作為成員變量,卻能在實現多線程下數據不共享,作為線程局部變量?

ThreadLocal的原理

回到一開始的圖:

  • ThreadLocal并不實際存儲數據,而是作為一個工具類,提供get、set方法根據當前線程用于操作當前線程中的實際數據
  • 每個Thread(線程)中都持有一個ThreadLocal.ThreadLocalMap類型的成員變量,初始值為null。ThreadLocal的get、set方法實際上就是在操作這個成員變量。
  • ThreadLocalMap持有一個Entry[]類型的成員變量table,類似JDK1.7的HashMap中的Entry[] table 可以類比學習
  • Entry key為ThreadLocal<?>類型的的弱引用,value為Object類型的強引用

ThreadLocal的set()

public void set(T value) {//1、獲取當前線程(調用者線程)Thread t = Thread.currentThread();//2、獲取當前線程的ThreadLocalMap變量ThreadLocalMap map = getMap(t);//3、如果map不為null,就直接添加本地變量,key為當前定義的ThreadLocal變量的this引用,值為添加的本地變量值if (map != null)map.set(this, value);//4、如果map為null,說明首次添加,需要首先創建出對應的mapelsecreateMap(t, value); }ThreadLocalMap getMap(Thread t) {//獲取線程的threadLocalsreturn t.threadLocals; }void createMap(Thread t, T firstValue) {//創建線程的threadLocals, this為ThreadLocal<?>的引用t.threadLocals = new ThreadLocalMap(this, firstValue); }

ThreadLocal的get()

在get方法的實現中,首先獲取當前調用者線程,如果當前線程的threadLocals不為null,就直接返回當前線程綁定的本地變量值,否則執行setInitialValue方法初始化threadLocals變量。在setInitialValue方法中,類似于set方法的實現,都是判斷當前線程的threadLocals變量是否為null,是則添加本地變量(這個時候由于是初始化,所以添加的值為null),否則創建threadLocals變量,同樣添加的值為null。
引用自:https://www.cnblogs.com/fsmly/p/11020641.html#_label0

public T get() {//1、獲取當前線程Thread t = Thread.currentThread();//2、獲取當前線程的threadLocals變量ThreadLocalMap map = getMap(t);//3、如果threadLocals變量不為null,就可以在map中查找到本地變量的值if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//4、執行到此處,threadLocals為null,調用該更改初始化當前線程的threadLocals變量return setInitialValue(); }private T setInitialValue() {//protected T initialValue() {return null;}T value = initialValue();//獲取當前線程Thread t = Thread.currentThread();//以當前線程作為key值,去查找對應的線程變量,找到對應的mapThreadLocalMap map = getMap(t);//如果map不為null,就直接添加本地變量,key為當前線程,值為添加的本地變量值if (map != null)map.set(this, value);//如果map為null,說明首次添加,需要首先創建出對應的mapelsecreateMap(t, value);return value; }

ThreadLocal操作總結

根據上述源碼的分析可知:ThreadLocal最終操作的都是調用線程的ThreadLocalMap成員變量,因此不同線程使用同一個ThreadLocal成員變量互不相擾。
每個線程內部有一個ThreadLocal.ThreadLocalMap類型threadLocals的成員變量,該變量的類型為類似于HashMap,其中的key為當前定義的ThreadLocal變量的this引用,value為使用set方法設置的值。每個線程的本地變量存放在自己的本地內存變量threadLocals中,如果當前線程一直不消亡,那么這些本地變量就會一直存在(所以可能會導致內存溢出),因此使用完畢需要將其remove掉。

ThreadLocal使用不當造成內存泄漏問題

弱引用

弱引用(這里討論ThreadLocalMap中的Entry類的重點):如果一個對象只具有弱引用,那么這個對象就會被垃圾回收器GC掉(被弱引用所引用的對象只能生存到下一次GC之前,當發生GC時候,無論當前內存是否足夠,弱引用所引用的對象都會被回收掉)。弱引用也是和一個引用隊列聯合使用,如果弱引用的對象被垃圾回收期回收掉,JVM會將這個引用加入到與之關聯的引用隊列中。若引用的對象可以通過弱引用的get方法得到,當引用的對象唄回收掉之后,再調用get方法就會返回null

ThreadLocalMap內部實際上是一個Entry數組:

  • Entry是繼承自WeakReference(弱引用)的一個類。
  • Entry的key是指向ThreadLocal的弱引用,value一般為強引用

當一個線程調用ThreadLocal的set方法設置變量的時候,當前線程的ThreadLocalMap就會存放一個記錄(Entry),這個記錄的key值為ThreadLocal的弱引用,value就是通過set設置的值。
如果當前線程一直存在且沒有調用該ThreadLocal的remove方法,此時存在兩種情況:

  • ThreadLocalMap之外存在ThreadLocal的引用:那么當前線程中的ThreadLocalMap中會存在對ThreadLocal變量的引用和value對象的引用,無法進行垃圾回收,導致這些本地變量一直存在,可能會出現內存溢出
  • ThreadLocalMap之外不存在ThreadLocal的引用:因為ThreadLocalMap中的Entry的key使用的是ThreadLocal對象的弱引用,所以下一次垃圾回收時ThreadLocal(key)將被回收。此時ThreadLocalMap中就可能存在key為null但是value不為null的現象,出現內存泄漏。

因此每次使用完ThreadLocal,建議調用它的remove()方法,清除數據,避免內存泄漏

內存泄漏問題總結

ThreadLocalMap中的Entry的key使用的是ThreadLocal對象的弱引用,在沒有其他地方對ThreadLocal存在依賴時,ThreadLocalMap中的ThreadLocal對象(即key)就會被回收,而如果其他地方存在ThreadLocal的引用則不會被回收。當key被回收時,Map中就可能存在key為null但是value不為null的現象,出現內存泄漏。

因此每次使用完ThreadLocal,建議調用它的remove()方法,清除數據,避免內存泄漏。

參考

  • ThreadLocal
  • Java中的ThreadLocal詳解
  • TheadLocal 引起的內存泄露故障分析

總結

以上是生活随笔為你收集整理的一文搞懂ThreadLocal及相关的内存泄露问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。