0. bg
最近在项目中用到threadlocal,threadLocal理解起来很简单,就是和当前线程绑定的一个map,使用get/set去拿到与线程名的key-value。那么问题来了,1.当一个线程中存在多个ThreadLocal变量,线程是如何通过get去得到value的呢?如下:
public class Test {
ThreadLocal<String> arg1=new ThreadLocal<>();
ThreadLocal<String> arg2=new ThreadLocal<>();
ThreadLocal<String> arg3=new ThreadLocal<>();
public static void main(String[] args) {
Test test=new Test();
test.arg1.get(); //如何去映射得到value值
test.arg2.get();
}
}
- 两个不同的类中,Test2去获取到同一个线程中的Test存储在ThreadLocal中的值的过程是什么样的?如:
public class Test2{
//同一个线程中,去获得test中的threadLocal的值
public static void main(String[] args) {
Test test1=new Test();
test1.arg1.get();
test1.arg2.get();
}
}
3. 如何使用ThreadLocal在同一个线程中优雅的传值呢?如问题2中,当在test1中设置了threadLocal对象的值,在test2中我们无法得到实例test1,那么我们如何去拿到threadLocal变量?
so带着上面的三个问题探索ThreadLocal.
1. ThreadLocal源码层面
从注释出发:
ThreadLocal变量通常是类中与线程关联的私有静态域:一个线程私有ThreadLocal。例如:类可以通过ThreadLocal给每个线程生成一个独一无二的标识符。
每个线程都有一个本地threadlocal副本变量的隐式的引用,当线程销毁或没有其他引用指向该线程的threaLocal的副本,则这些副本将被回收
继续往下:ThreadLocals依赖一个依附于每个线程的线性hash表,hash表中threadLocal对象(计算hash值,这个hash值是threadLocal自定义的算法)作为key。
继续往下我们发现ThreadLocal规定了未经set()而去使用get()方法获得的值为null,如果我们希望改变这get()得到的null值,可以继承ThreadLocal并重写
initialValue()
,当然还有内部类SuppliedThreadLocal
,它重写了initialValue()方法
,允许我们提供一个supplier
去提供初始值。如下:
ThreadLocal t= ThreadLocal.withInitial(()->"1");
System.out.println(t.get()); //打印 1
现在到了最重要的方法之一:get()
: 它会得到当前线程的在局部变量在threadLocal中的值,如果没有值,则调用initialValue()
获得返回值。get()方法如下:
public T get() {
Thread t = Thread.currentThread(); //当前线程
ThreadLocalMap map = getMap(t);//核心 threadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
对ThreadLocalMap进行研究:显然,它是一个map,每个线程都有一个threadLocalMap,那么对应上面两个问题,很难知道key是什么东西。出现问题:什么是弱引用? --弱引用对象每次GC都会被回收。 继续往下走发现,ThreadLocalMap的key就是当前的ThreadLocal对象。那么问题一二解决了,再看ThreadLocal的get源码ThreadLocalMap.Entry e = map.getEntry(this);
这里的this即是当前threadLocal,故得到vaue。同时:问题:既然threadLocal只能管理一个变量,那么threadLocalMap中为什么要定义一个Entry的数组,为什么不定义一个Entry? ---> 参考上面的代码,当我们在一个类中,用到了多个ThreadLocal变量时,这个时候,每个threadLocal就在后面将key-value放到了Entry数组里面了。还应该注意到,ThreadLocal是static修饰的,那么是所有的线程公用同一个ThreadLocal对象。
set()
方法同理。至于ThreadLocalMap里面其他的方法,就不累赘分析了,类似集合Map。
2. threadLocal副本?为什么threadLocal要使用变量副本?
threadLocal从字面理解,这个类为每个线程都创建了一个本地变量,实际上是ThreadLocal为变量在每个线程中都创建了一个副本,使得每个线程都可以访问自己内部的副本变量
通常提到多线程,都会考虑变量同步的问题,但是ThreadLocal并不是为了解决多线程共享变量同步的问题,而是为了让每个线程的变量不互相影响,相当于线程之间操纵的都是变量的副本,自然就不用考虑多线程竞争的问题,也自然没有性能损耗。
3. 问题三解决
通过上述分析,我们知道ThreadLocal是与线程绑定的,而线程中又共享同一份ThreadLocalMap,key-value都存储在threadLocalMap中,那么我们只要在同一份ThreadLocal操作就可以了。即把ThreadLocal做一个封装,当成类的static对象即可。如下:
public class ThreadLocalArgs {
private static final ThreadLocal<String> argThreadLocal= new ThreadLocal<>();
public static void set(String argument){
argThreadLocal.set(argument);
}
public static String get(){
return argThreadLocal.get();
}
public static void unset(){
argThreadLocal.remove();//注意当前线程用完变量后要remove
}
}
那么在该线程的所有实例中,通过调用ThreadLocalArgs .argThreadLocal即可拿到变量的值。