天天看點

ThreadLocal 可能踩到的坑

背景

  • 首先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的情況:

    ​​

    ThreadLocal 可能踩到的坑

解決方案

  • 線上程池(包括web主線程)中使用 ThreadLocal 的時候,一定要考慮到 ThreadLocal 對象的生命周期是跟随線程的,會随着線程池的循環而一直存在。
  • 那麼在這種場景下,使用 ThreadLocal 就要注意這幾點:
    • Map、List、Set等集合對象,要考慮資料是否會一直增加,如果一直增加而不移除的話就會造成記憶體溢出。
    • 用作計數器的話,使用之後要清0,不然計數的數值會随着線程一直儲存下來。