ThreadLocal错误示范
1 接口返回对象封装到ThreadLocal
服务接口返回的Response对象有仨字段code,msg和data,每次成功的返回都是code=0,msg=success,只修改data部分。想着要不就把Response对象封装到ThreadLocal中,每次从ThreadLocal去get,然后setData,然后return。
这样做的话,服务如果有300个线程的池子,那就只需要300个Response对象,不用每次新请求来都重新new一遍啦,可以减少gc。看似很理想,实际上会出问题,服务常用的网络框架netty需要对return的这个Response对象序列化,序列化过程是扔到一个队列中来处理,是将对象引用扔过来,如果使用ThreadLocal,在return之后该线程结束,下一个新请求如果被该线程承接,就会set成新的data,这时候netty队列可能还没处理到这个对象,就会导致结果返回到client端之前就被篡改。
所以不要对接口返回对象封装成ThreadLocal,就每次new就好啦。
2 线程池中的线程可以启动线程,并且CallerRun拒绝策略
使用ThreadLocal有个很好的习惯就是在finally中remove,但是当使用ForkedJoinPool的时候或者线程池中线程可以从池子中拿新线程启动任务的时候,可能会出现空指针,这是怎么回事呢。
池子中一个线程T1需要启动一个新的任务他就从池子中去拿,但是发现线程池满,就被拒绝了,然后策略是CallerRun,那就自己来阻塞式的执行这个新的任务。每个任务都是需要getThreadLocal最后removeThreadLocal。
因为执行新任务的时候,在任务临终前remove掉了ThreadLocal,导致栈退上来之后,当前T1上下文ThreadLocal无了,继续往下执行就get不到了。
所以使用线程池的时候,尽量避免池子中的线程可以创建新的线程。使用ForkedJoinPool的时候,自己要特别注意ThreadLocal的处理,可以在线程开始的地方先get出内容,防止后面get不到了。