当前位置: 首页 > 科技观察

ThreadLocal使用不规范导致的Bug让人泪目

时间:2023-03-15 23:29:19 科技观察

ThreadLocal一般用于线程间的数据隔离。通过在ThreadLocal中缓存数据,可以大大提高性能。但是,如果Threadlocal使用不当,可能会导致意想不到的bug和内存泄露。由于线程复用导致信息混乱的bug,有时我们在一个接口中缓存一些ThreadLocal的数据,但是我们必须意识到这些处理请求的线程是tomcat提供的,tomcat提供的线程是配置在一个线程中的水池。换句话说,线程可以被重用。如果线程被复用,没有及时重置ThreadLocal中的数据,就会造成数据被乱用。以下面接口为例,首先获取当前线程中保存的数据信息,将参数中的名称保存到ThreadLocal中,然后再次获取。@GetMapping(value="/threadLocal")publicResponseEntitythreadLocal(Stringname){Stringbefore=Thread.currentThread().getName()+":"+threadLocal.get();//先取值,理论上应该是nullSystem.out.println("before:"+before);threadLocal.set(名字);Stringafter=Thread.currentThread().getName()+":"+threadLocal.get();//设置参数值后,getSystem.out.println("after:"+after);returnResponseEntity.ok().build();}为了尽快复现线程重用导致的问题,我们将servlet.tomcat。threads.max设置为1,以便每个请求使用相同的线程。第一次请求接口时,数据看起来正常;但是第二次请求该接口时,可以看到线程还是http-nio-8080-exec-1,只是之前打印出了第一次请求的参数test。这是没有及时重置ThreadLocal导致的数据错误。正确的纠正姿势的方法是在处理完接口后及时清理ThreadLocal。@GetMapping(value="/threadLocal")publicResponseEntitythreadLocal(Stringname){try{Stringbefore=Thread.currentThread().getName()+":"+threadLocal.get();//获取第一个值,理论上应该为nullSystem.out.println("before:"+before);threadLocal.set(名字);Stringafter=Thread.currentThread().getName()+":"+threadLocal.get();//设置参数值后,再次获取System.out.println("after:"+after);}finally{//清理数据threadLocal.remove();}returnResponseEntity.ok().build();}可能有朋友会说,每次都用tryfinally处理线程数据,太麻烦了。其实我们可以使用拦截器或者过滤器来自动帮我们完成数据的初始化和清洗。最后,我们在写业务代码的时候,正确理解线程的完整生命周期和执行原理,其实对我们提高代码的健壮性是很有帮助的。有时我们会觉得底层原理很无聊。开发业务就是写、增、删、改、查,很少用到多线程,只是没有意识到我们的代码一直运行在tomcat提供的线程池中,tomcat本身就是一个多线程的线程环境。除了tomcat的线程池,我们自定义的线程池也有这个问题。大家可以看看自己的业务代码有没有踩过这个坑。