背景
- 首先ThreadLocal 不做太多的介紹,在多線程場景下有着廣泛的應用。
- 最近在實作OneLog方法級日志的時候,使用了 ThreadLocal 作為緩存和計數器,在調試過程中,發現有一些場景會出現資料錯亂和記憶體溢出的問題。
詳情
- OneLog方法級日志使用 ThreadLocal 有兩個場景,一個是将方法入參資訊緩存起來,然後在方法結束之後與傳回結果進行組裝。另一個是作為計數器使用,保證循環方法的場景下,不會列印太多的日志。
- 實際使用的時候發現了問題,ThreadLocal 對象的生命周期是依賴于線程的,那麼對于一個web應用來說,tomcat的線程是循環使用的,也就是說在web應用中,主線程下 ThreadLocal 對象永遠不會被回收!
- 下面是問題複現示例代碼:
@Controller
public class TestThreadLocalController {
//計數器
private final ThreadLocal<Integer> counter = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
@RequestMapping("/testThreadLocal")
public @ResponseBody Integer testThreadLocal() {
doSomething();
return counter.get();
}
private void doSomething() {
counter.set(counter.get()+1);
}
}
-
執行多次web接口調用,當tomcat線程出現重複使用的時候,即可看到傳回值大于1的情況:
解決方案
- 線上程池(包括web主線程)中使用 ThreadLocal 的時候,一定要考慮到 ThreadLocal 對象的生命周期是跟随線程的,會随着線程池的循環而一直存在。
- 那麼在這種場景下,使用 ThreadLocal 就要注意這幾點:
- Map、List、Set等集合對象,要考慮資料是否會一直增加,如果一直增加而不移除的話就會造成記憶體溢出。
- 用作計數器的話,使用之後要清0,不然計數的數值會随着線程一直儲存下來。