又是一个风和日立的早上,这天小美遇到了一个难题:java
原来是小美在作服务鉴权的时候,须要根据每一个请求获取token:数据库
//获取认证信息 Authenticationauthentication = tokenProvider.getAuthentication(jwt); //设置认证信息 SecurityContext.setAuthentication(authentication);
而后通过层层的调用,在业务代码里根据认证信息进行权限的判断,也就是鉴权。小美内心琢磨着,若是每一个方法参数中都传递SecurityContext信息,就显的太过冗余,并且看着也丑陋。那么怎么才能隐式传递参数呢?这个固然难不倒小美,她决定用ThreadLocal来传递这个变量:数组
classSecurityContextHolder{ private static final ThreadLocal<SecurityContext>contextHolder = newThreadLocal<SecurityContext>(); public SecurityContextgetContext(){ SecurityContextctx = contextHolder.get(); if(ctx==null){ contextHolder.set(createEmptyContext()); } returnctx; } }
......(省略没必要要的)多线程
SecurityContextHolder.getContext().setAuthentication(authentication);ide
总体思路上就是将SecurityContext放入ThreadLocal,这样当一个线程缘起生灭的时候,这个值会贯穿始终。
完美,小美喜滋滋的提交了代码,而后发布出去了。
结果次日系统就出现异常了,明明是这个用户A的发起的请求,到了数据库中,却发现是操做人是用户B的信息,一时间权限大乱。
完蛋了。。。spa
这是为何呢?线程
咱们得先扯一扯ThreadLocal,Thread,ThreadLocalMap之间的爱恨情仇。设计
图片解说:3d
因为Thread内部的ThreadLocal.ThreadLocalMap对象是每一个线程私有的,因此作到了数据独立。code
因而咱们知道了ThreadLocal是如何实现线程私有变量的。
可是问题来了,若是线程数不少,一直往ThreadLocalMap中存值,那内存岂不是要撑死了?
固然不是,设计者使用了弱引用来解决这个问题:
static class Entry extends WeakReference<ThreadLocal<?>>{ Object value; Entry(ThreadLocal<?> k,Object v){ super(k); value=v; } }
不过这里的弱引用只是针对key。每一个key都弱引用指向ThreadLocal。当把ThreadLocal实例置为null之后,没有任何强引用指向ThreadLocal实例,因此ThreadLocal将会被GC回收。然而,value不能被回收,由于当前线程存在对value的强引用。只有当前线程结束销毁后,强引用断开,全部值才将所有被GC回收,由此可推断出,只有这个线程被回收了,ThreadLocal以及value才会真正被回收。
听起来很正常?
那若是咱们使用线程池呢?常驻线程不会被销毁。这就完蛋了,ThreadLocal和value永远没法被GC回收,形成内存泄漏那是必然的。
而咱们的请求进入到系统时,并非一个请求生成一个线程,而是请求先进入到线程池,再由线程池调配出一个线程进行执行,执行完毕后放回线程池,这样就会存在一个线程屡次被复用的状况,这就产生了这个线程这次操做中获取到了上次操做的值。
怎么办呢?
解决办法就是每次使用完ThreadLocal对象后,都要调用其remove方法,清除ThreadLocal中的内容。
public class ThreadLocalTest{ static ThreadLocal<AtomicInteger> sequencer = ThreadLocal.withInitial(()->newAtomicInteger(0)); static class Task implements Runnable{ @Override public void run(){ int value = sequencer.get().getAndIncrement(); System.out.println("-------"+value); } } public static void main(String[]args){ ExecutorService executor = Executors.newFixedThreadPool(2); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.shutdown(); } }
输出: 0 1 0 2 3 1
这个就是错误的,正确代码以下:
public class ThreadLocalTest{ static ThreadLocal<AtomicInteger> sequencer = ThreadLocal.withInitial(()->newAtomicInteger(0)); static class Task implements Runnable{ @Override public void run(){ int value = sequencer.get().getAndIncrement(); System.out.println("-------"+value); sequencer.remove(); } } public static void main(String[]args){ ExecutorService executor = Executors.newFixedThreadPool(2); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.execute(newTask()); executor.shutdown(); } }