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();//先获取值,实践上应该是nullSystem.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的线程池,咱们自定义的线程池其实也会有这个问题,大家能够看看本人的业务代码是否踩过这个坑。