天天看點

項目中的ThreadLocal記憶體洩漏問題

ThreadLocal概念

ThreadLocal,線程局部變量。

ThreadLocal能夠存放一個線程級别的變量,其本身能夠被多個線程共享使用,并且又能夠達到線程安全的目的。作用就是在多線程環境下去保證成員變量的安全。

常用的方法:get()/set()/initialValue()/remove()

常用場景

  • ThreadLocal最常用的地方就是為每個線程綁定一個資料庫連接配接,HTTP請求,使用者身份資訊等,這樣一個線程的所有調用到的方法都可以非常友善地通路這些資源。
  • Hibernate的Session 工具類HibernateUtil
  • 通過不同的線程對象設定Bean屬性,保證各個線程Bean對象的獨立性。

原理

項目中的ThreadLocal記憶體洩漏問題

在每個線程Thread内部有一個ThreadLocalMap,這是用來存儲實際的變量副本的,鍵值key為目前ThreadLocal變量,value為變量副本。

初始時,在Thread裡面,ThreadLocalMap為空,當通過ThreadLocal變量調用get()方法或者set()方法,就會對Thread類中的ThreadLocalMap進行初始化,并且以目前ThreadLocal變量為鍵值,以ThreadLocal要儲存的副本變量為value,存到ThreadLocalMap。

然後在目前線程裡面,如果要使用副本變量,就可以通過get方法在ThreadLocalMap裡面查找。 一個Thread中隻有一個ThreadLocalMap,一個ThreadLocalMap中可以有多個ThreadLocal對象,其中一個ThreadLocal對象對應一個ThreadLocalMap中的一個Entry(即一個Thread可以依附有多個ThreadLocal對象)。

[ThreadLocal原理參考]: https://blog.csdn.net/dakaniu/article/details/80829079

項目中的案例

場景:

在寫公司的一個中間件項目時,需要寫一個接口供外部調用。通過攔截器對外部請求進行日志記錄,需要記錄外部請求封包,響應封包,請求耗費時間。記錄消耗時間時,考慮到多個線程之間的請求會串用局部變量,是以考慮使用ThreadLocal來包裝上面的變量。

private static ThreadLocal<Date> startTime = new ThreadLocal<>();
private static ThreadLocal<Date> endTime = new ThreadLocal<>();
private static ThreadLocal<String> requestThreadLocal = new ThreadLocal<>();
private static ThreadLocal<String> responseThreadLocal = new ThreadLocal<>();
//在擷取到開始時間和結束時間後,進行相減,得到耗費時間
//......
           

項目上生産後,發現很多請求日志的耗費時間顯示為負數。進行分析後,發現是ThreadLocal出現了記憶體洩漏的原因,如果線程被重複使用了,就會使得之前在ThreadLocal中的變量會被重複利用,導緻了不同請求的時間串用了。

解決方案:

在每個線程對ThreadLocal中的變量每一次使用完畢後,都要使用ThreadLocal的remove()方法來清除掉這個線程的變量,這樣就解決了問題。

其他注意點:

ThreadLocal使用時,最好要加static修飾,避免過多建立,浪費資源。

繼續閱讀