背景
- 首先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,不然计数的数值会随着线程一直保存下来。