共计 1619 个字符,预计需要花费 5 分钟才能阅读完成。
ThreadLocal 个别用于线程间的数据隔离,通过将数据缓存在 ThreadLocal 中,能够极大的晋升性能。然而,如果谬误的应用 Threadlocal,可能会引起不可预期的 bug,以及造成内存泄露。
因为线程重用导致的信息错乱的 bug
有时咱们会在一个接口中缓存某些数据到 ThreadLocal 中,然而咱们要意识到,解决申请的这些线程是由 tomcat 提供的,而 tomcat 提供的线程都是配置在一个线程池中的。
也就是说,线程是可能被重用的,如果线程一旦被重用,而 ThreadLocal 的数据没有及时重置,就会导致数据被凌乱应用。
以下方的接口为例,先获取以后线程中保留的数据信息,将参数中的 name 保留到 ThreadLocal 中当前,再获取一次。
@GetMapping(value = “/threadLocal”)
public ResponseEntity<Object> threadLocal(String name) {
String before = Thread.currentThread().getName() + “:” + threadLocal.get();
// 先获取值,实践上应该是 null
System.out.println(“before:” + before);
threadLocal.set(name);
String after = Thread.currentThread().getName() + “:” + threadLocal.get();
// 设置完参数值再获取一次
System.out.println(“after:” + after);
return ResponseEntity.ok().build();
}
复制代码
为了尽快复现线程重用导致的问题,咱们将 servlet.tomcat.threads.max 设置为 1,这样每次申请应用的都是同一个线程。
第一次申请接口,数据看起来很失常:
然而第二次申请接口时,能够看到线程依然是 http-nio-8080-exec-1,然而 before 却打印出了第一次申请的参数 test。
这就是因为没有及时重置 ThreadLocal 导致的数据谬误。
正确应用的姿态
修改的方法就是解决完接口之后要及时清理 ThreadLocal。
@GetMapping(value = “/threadLocal”)
public ResponseEntity<Object> threadLocal(String name) {
try {
String before = Thread.currentThread().getName() + ":" + threadLocal.get();
// 先获取值,实践上应该是 null
System.out.println("before:" + before);
threadLocal.set(name);
String after = Thread.currentThread().getName() + ":" + threadLocal.get();
// 设置完参数值再获取一次
System.out.println("after:" + after);
} finally {
// 清理数据
threadLocal.remove();
}
return ResponseEntity.ok().build();
}
复制代码
更优雅的解决形式
可能也有的敌人会说,每次都要应用 try finally 解决线程数据,未免也太麻烦了。其实,咱们能够应用拦截器或者过滤器主动帮咱们实现数据的初始化以及清理工作。
最初
咱们在写业务代码时,正确的了解线程的全生命周期以及执行原理,对咱们晋升代码的健壮性其实很有帮忙。有时咱们感觉底层原理很枯燥乏味,开发业务就是写增删改查,多线程用的也很少,但咱们只是没有意识到,咱们的代码始终跑在 tomcat 提供的线程池中,自身就是一个多线程的环境。
除了 tomcat 的线程池,咱们自定义的线程池其实也会有这个问题,大家能够看看本人的业务代码是否踩过这个坑。