ThreadLocal
简单的分析一下 ThreadLocal1.ThreadLocal可以做到数据的非显性赋值,且数据在线程之间是相互隔离的。
怎么做到的呢,其实原理很简单:给每个线程”绑定”各自的数据(多数情况就是new一个新的对象)。
简单的讲就是(伪代码):
Object obj = new Object()
↓
ThreadLocal.set(obj)
↓
thread.threadLocalMap.add(ThreadLocal,obj)
threadLocalMap是Thread的成员变量,ThreadLocalMap.class是ThreadLocal的内部类,ThreadLocal的set(Object)方法就是把这个Object对象保存到ThreadLocalMap中。每个Thread在调用ThreadLocal.set的时候,都会new一个新的Object对象,所以ThreadLocal.get的时候,Thread相当于取到了各自的Object成员变量,因此数据不会相互干扰。
2.为什么不直接给Thread添加成员变量,而要经过ThreadLocal来间接设置呢?我感觉也并不是不行,自定义一个MyThread继承Thread,把想要的数据定义成员变量即可,可以达到相同的目的。但是有个问题,如果Thread1想要的数据类型是Integer,Thread2想要的是String,岂不是又得修改MyThread,假如又出现了Thread3呢…挺麻烦的,还不如直接使用Api定义好的ThreadLocal,针对不用的T使用不同的ThreadLocal
3.ThreadLocalMap中的EntryEntry才是ThreadLocalMap中真正存储数据的地方,它继承WeakReference,构造函数
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
ThreadLocalMap中的Entry为什么被定义成数组?
因为同一个Thread可能对应多个ThreadLocal,不同的ThreadLocal.set设置的数据也不一样,这样使用Entry数组来储存所有的数据比较方便。
Entry的key为什么需要定义成弱引用?
假设key是强引用,那么:把ThreadLocal引用置为空(=null)的时候,Entry的key依然持有ThreadLocal对象的强引用,那么这个ThreadLocal不会销毁。这种情况下怎么才能销毁对应的Entry对象呢?我认为只能在ThreadLocal置为空的同时,也把对应的Entry置为空,两者同步进行。
key是弱引用:那么把ThreadLocal引用置为空后,系统gc后,由于对应的Entry的key是弱引用,所以ThreadLocal对象被销毁,此时的Entry应该变成了Entry(null,value),那value什么时候销毁呢,因为key是null,value已经没有存在的意义。这里,每次使用ThreadLocal.set/get,系统都会调用以下方法销毁key==null的value
expungeStaleEntry(int staleSlot)
既然强引用的情况也能达到销毁Entry的目的,为啥还要设计成弱引用,我觉得设计者的考虑是:
强引用的情况,每次回收前,需要先调用一个类似ThreadLocal.clear()的函数销毁Entry,再调用ThreadLocal=null。
弱引用情况,只需要ThreadLocal=null,在gc的时候,系统会自动回收Entry的key对应的ThreadLocal对象。这样,ThreadLocal只需要对外提供get和set接口即可,显得逻辑上更加紧凑。
4.实战
在一个操作日志的处理类中,是使用AOP的方式实现的,要记录每个带有@Log注解的接口的请求时长,简化后的代码如下
private static final ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
/**
* 处理请求前执行
*/
@Before(value = "@annotation(controllerLog)")
public void doBefore(JoinPoint joinPoint, Log controllerLog) {
StopWatch stopWatch = new StopWatch();
KEY_CACHE.set(stopWatch);
stopWatch.start();
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
StopWatch stopWatch = KEY_CACHE.get();
stopWatch.stop();
KEY_CACHE.remove();
}
每个接口运行在各自的线程,
假设ThreadA调用KEY_CACHE.set(stopWatch),
实际是ThreadA.ThreadLocalMap.set(stopWatch),
当KEY_CACHE.get()的时候,
实际是Thread.currentThread().ThreadLocalMap.get(stopWatch),每个线程拿到属于自己的stopWatch,然后用stopWatch计算时长。
总结:ThreadLocal核心作用是为每个使用它的线程都创建一个独立的变量副本,使得每个线程都能操作属于自己的专属变量。