前言
- 随着互联网的进步,互联网企业对安全越来越重视,以前对于客户的信息都是通过前端传递后端接受来完成,可这样就非常不安全但凡懂点结束的人都可以随意获取到用户信息
- 于是大家就开始使用token来进行传递数据,后端拿到token进行解析然后取到里面需要的值进行操作但是只要有用到的地方就需要解析很麻烦
- 为了方便就演化成将token解析后存入InheritableThreadLocal当前线程如果需要的话就直接通过本地线程就可以直接拿到数据很方便
问题
- 线程池里面的线程对于本地本地线程里面的数据会一直保留
问题复现
示例1
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,3,0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(20),new ThreadPoolExecutor.AbortPolicy());
InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
AtomicBoolean flag = new AtomicBoolean(true);
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
if(flag.get()){
threadLocal.set("张三吃豆芽" + Thread.currentThread().getName());
flag.set(false);
}
System.out.println(Thread.currentThread().getName() + "==" + threadLocal.get());
});
}
}
打印结果:
pool-1-thread-1==张三吃豆芽pool-1-thread-1
pool-1-thread-2==null
pool-1-thread-3==null
pool-1-thread-2==null
pool-1-thread-1==张三吃豆芽pool-1-thread-1
pool-1-thread-3==null
pool-1-thread-2==null
pool-1-thread-3==null
pool-1-thread-1==张三吃豆芽pool-1-thread-1
pool-1-thread-2==null
- 创建一个核心数3的线程池
- 创建一个主线程的本地线程对象
- 循环十个任务只有第一次进来的时候给本地线程塞值,因为InheritableThreadLocal的特性这里是可以获取到主线程的本地线程对象且可以塞值
- 通过打印结果我们可以发现线程1使用了三次且每次都有其他任务里面塞的数据
有同学会说,我使用完把这个线程里面的本地线程数据删除不就行了,确实这确实可以 但是我们一般解析token塞入数据是不是都是在主线程做的操作 然后子线程再去复制主线程的本地线程数据我们继续往下看
示例2
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,3,0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(20),new ThreadPoolExecutor.AbortPolicy());
InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("张三吃豆芽" + Thread.currentThread().getName());
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + "==" + threadLocal.get());
});
}
threadLocal.remove();
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + "==" + threadLocal.get());
});
}
打印结果:
pool-1-thread-1==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-2==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-1==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-2==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-1==张三吃豆芽main
pool-1-thread-3==张三吃豆芽main
pool-1-thread-2==张三吃豆芽main
- 这段代码和上段代码不同的是我们在本地线程塞值的时机改成了在主线程上面
- 之后跑十个任务,这时候线程池里面的三个线程都复制了主线程里面的本地线程数据(这里的复制不是指针是new出来的本地线程对象)
- 删除主线程的本地线程数据
- 再次使用线程池里面的线程,发现数据还是在(也证明了子线程是深拷贝主线程的本地线程数据)
问题原因
- 不难看出,因为我们使用的是InheritableThreadLocal,子线程可以复制主线程本地线程的数据导致子线程的InheritableThreadLocal字段也有数据,如果使用的是线程池那么当前线程里面的数据就可能被误用
解决办法
目前主线程只能通过当前线程结束的时候进行remove()操作删除本地线程里面的数据,但是如果使用了线程池里面的数据那除非是重新塞值,其他我也想不到好的办法像我们平时使用的tomcat线程池里面如果进行了复制请求进来会重新塞值这个不会有什么问题,但是如果这时候有定时任务或者事件之类的进来不会重新塞值就可能会引发这样的问题