公司新来了一位00后java美女程序员,因为年龄太小了,我就叫她幺妹吧。有一天幺妹莫名其妙走到我办公桌旁边,柔情地看着我说:“哥,你知道怎么用ThreadLocal吗?能给我讲讲它的底层实现吗?”,她来得有点突然,问题问得也有点突然,恰好我当时手头上有事正在忙,心里是一千个不愿意,不过程序员都知道,在我们这个行业女同志是很少的,特别是美女同志就更少了,心里一番斗争下来后,还是答应给人家讲讲,毕竟人家是刚入行不久。
“问你个问题,在写web应用的时候,如果用户登录成功了,并且将用户信息存放到了Session中,此时想要在Service层的代码中获取用户信息该怎么办?”我理了理思路,然后问她;
“在Controller类中从Session中获取到用户信息,然后在Service对应的方法上添加参数传递”她想了想然后回答;
“嗯,这个方法是可以实现的,但是如果我所有的Service层代码都需要获取用户信息,那是不是要累死啊,还有,如果有一天这个用户的类名改了,是不是也要改疯?这很显然不是一种好的思路,代码量增加了,还不利于扩展和维护,是吧?”。
“有道理,果然是大神,有没有什么好方法呢?教教我”,她有点着急。
“其实也很简单,只要添加一个拦截器,在拦截器里面定义一个ThreadLocal对象,然后获取用户信息,并将用户信息set到这个ThreadLocal对象中,这样在同一个线程中不管是Service层,或者任何其他类中调用ThreadLocal的get方法就轻松获取到”,我笑着回答。
“这么简单啊,但是为什么调用ThreadLocal的set方法后,在其他地方都可以获取到呢?其中的原理是什么呢?”,她追问。
“好吧,给你讲讲原理”,我打开电脑,写了一段简单的代码,然后分析原理:
要弄懂ThreadLocal的底层实现原理,跟踪set和get方法,
先看set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
以上流程如下:
1.先是得到当前线程;
2.然后获取线程的threadLocals对象;
3.判断threadLocals对象是否为空,如果为空(没用过当然为空),就调用createMap去创建threadLocals对象,这个threadLocals的类是ThreadLocalMap,这个ThreadLocalMap有点类似HashMap,暂时可以简单的理解为key-value的集合类(后续咱们会单独讲HashMap的原理),其关系如下图所示:
4.如果不为空,则直接调用这个map对象的set方法,注意,这里的key是this,value是我们需要设置的值,这个this就是咱们的ThreadLocal对象。
然后咱们来看get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
这个方法简单,从当前线程的threadLocals这个map对象中获取到key为this对应的value值,注意这里的key也是this。
好,主要原理对应的方法已经分析了,主要思想也就在这里了,一句话总结下:
每个线程中都维护了一个Map对象,set的值就是存放在线程自己的这个Map对象中,而key就是这个ThreadLocal对象,在get的时候也是从每个线程自己的这个Map中去获取的,于是实现了不同线程之间数据的隔离。