前言最近在使用ThreadLocal的时候遇到了一个生产问题。一早接到一个业务员的电话,说登录后系统总是莫名其妙的报错,而且有点随机……昏昏欲睡的脑袋瞬间清醒了。我问是哪个模块报错的,是不是在操作一些具体的功能后报错。答案是否定的。任何函数操作都会随机报错?错误。一时间有些懵,脑子里不断回想这段时间有没有新的版本。没有,最近没有大版本。都是小改动,不可能影响到所有的业务模块。赶紧去公司~到了公司后,赶紧去机房,查看后台日志,发现报空指针异常,然后继续定位代码,发现这段代码是从链接日志模块报告。仔细看了代码,发现是从链接日志报错的。这段代码貌似没问题,而且这个模块已经投产好几个月了,从来没有出现过类似的错误。我按照代码,是从ThreadLocal取值的,第一反应是链接日志有问题。让我们暂时忽略它。业务紧急,先重启应用。说来也怪,应用重启后没有报错。真是太神奇了。现在我更好奇了。我在开发环境调试。代码逻辑伪代码如下//伪代码1,ThreadLocal初始化2.ThreadLocalthreadlocal=newThreadLocal();3、if(threadlocal.get()==null)threadlocal.set(XX)4....相关业务代码5、threadlocal.get()获取链接日志相关信息,进行相关处理6、乍一看,threadlocal.remove()没问题,但是由于异常信息导致第4步出现异常,捕获到的catch却没有在finally中操作threadlocal.remove(),又因为第3步的空判断无效对于线程(这个线程已经被设置值),所以这个线程是被污染的,也就是每次使用这个被污染的线程都会报错,这就是production的随机报错是怎么来的。关于bug修复就不多说了。至此问题已经解决。经验教训:在使用ThreadLocal的时候,一定要记得考虑清楚场景,考虑所有情况。下面是对没有remove操作的ThreadLocal的一些操作staticThreadLocalthreadLocal=newThreadLocal<>();//没有remove操作的ThreadLocal的表现publicstaticvoidmain(String[]args)throwsInterruptedException{//创建线程池ExecutorServicepool=Executors.newFixedThreadPool(2);for(inti=0;i<=5;i++){finalintcount=i;pool.execute(()->{Integerinteger=threadLocal.get();System.out.println("******************Thread"+Thread.currentThread().getName().substring(7)+"设置threadlocal前的值为:"+integer);if(StringUtils.isEmpty(threadLocal.get())){threadLocal.set(count);}System.out.println("******************thread"+Thread.ThecurrentThread().getName().substring(7)+"中的value是:"+threadLocal.get());});Thread.sleep(100);}}控制台打印效果如下,搞错了answerandproceedTheremoveoperationstaticThreadLocalthreadLocal=newThreadLocal<>();//ThreadLocal对remove操作的表现npublicstaticvoidmain(String[]args)throwsInterruptedException{//创建线程池ExecutorServicepool=Executors.newFixedThreadPool(2);for(inti=0;i<=5;i++){finalintcount=i;pool.execute(()->{Integerinteger=threadLocal.get();System.out.println("*********************Thread"+Thread.currentThread().getName().substring(7)+"设置threadlocal前的值为:"+integer);if(StringUtils.isEmpty(threadLocal.get())){threadLocal.set(count);}System.out.println("******************线程"+Thread.currentThread().getName().substring(7)+"里面的值为:"+threadLocal.get());threadLocal.remove();});Thread.sleep(100);}}控制台打印效果如下,得到正确答案,remove操作报错staticThreadLocalthreadLocal=newThreadLocal<>();//没有remove操作的ThreadLocal的表现publicstaticvoidmain(String[]args)throwsInterruptedException{//创建线程池ExecutorServicepool=Executors.newFixedThreadPool(2);for(inti=0;i<=5;i++){finalintcount=i;pool.execute(()->{try{Integerinteger=threadLocal.get();System.out.println("********************线程“+Thread.currentThread().getName().substring(7)+"设置threadlocal前的值为:"+integer);if(StringUtils.isEmpty(threadLocal.get())){threadLocal.set(count);}if(Thread.currentThread().getName().contains("thread-1")){thrownewRuntimeException();}System.out.println("*****************thread"+Thread.currentThread().getName().substring(7)+"里面的值为:"+threadLocal.get());threadLocal.remove();}catch(Exceptione){}});Thread.sleep(100);}}控制台打印效果如下,虽然执行了catch,但是remove操作没有执行最后,结果是修改错误答案得到最终代码staticThreadLocalthreadLocal=newThreadLocal<>();//没有remove操作的ThreadLocal的表现publicstaticvoidmain(String[]args)throwsInterruptedException{//创建一个线程池ExecutorServicepool=Executors.newFixedThreadPool(2);for(inti=0;i<=5;i++){finalintcount=i;pool.execute(()->{try{Integerinteger=threadLocal.get();System.out.println("******************Thread"+Thread.currentThread().getName().substring(7)+"设置前的值tingthreadlocal是:“+整数”;如果(StringUtils。isEmpty(threadLocal.get())){threadLocal.set(count);}if(Thread.currentThread().getName().contains("thread-1")){thrownewRuntimeException();}System.out.println("******************thread"+Thread.currentThread().getName().substring(7)+"里面的值为:"+threadLocal.get());}catch(Exceptione){.....}finally{threadLocal.remove();}});Thread.sleep(100);}}ThreadLocal用于线程间的数据隔离。说到线程间的数据隔离,我们也可以想到synchronized或者其他锁来实现线程间的安全问题。ThreadLocal适用于什么样的业务场景1.使用threadlocal来存储数据库连接。如果一个线程请求需要同时更新Goods表和Goods_Detail表,如果直接创建两个数据库连接,那么事务是无法保证的。数据库连接池使用ThreadLocal来存储数据库连接对象Connection,这样每次操作数据库表时,都使用同一个对象来保证事务。2.解决SimpleDataFormat的线程安全问题3.基于hreadlocal动态切换数据源4.使用ThreadLocal存储Cookie对象。在这个Http请求中,可以随时通过简单的方式获取cookies。当ThreadLocal被设置绑定到当前线程后,如果线程希望当前线程的子线程也能拿到这个值,这时候InheritableThreadLocal就派上用场了,如何传递给子线程呢?InheritableThreadLocal的具体使用如下://CreateInheritableThreadLocalstaticThreadLocalthreadLocaltest=newInheritableThreadLocal<>();publicstaticvoidmain(String[]args){//主线程设置值threadLocaltest.set(100);newThread(()->{//子线程获取值Integernum=threadLocaltest.get();//子线程获取值并打印出来System.out.println(Thread.currentThread().getName()+"子类获得的值"+num);//输出:Thread-0子类获得的值为100})。开始();}