ThreadLocal的效果很明显,对同一个变量,各个线程能保存自己的值,使用起来也很简单,就三个方法,get , set , remove 。简单操作的背后,jdk帮我们隐藏了很多逻辑,今天我们一起看看threadLocal的使用的背后的原理
ThreadLocal的使用
使用没准备用多少篇辐来写,因为使用就三个操作,初始化时set,使用时 get , 不用时remove 下面用一段简单的代码演示下
//初始化两个threadLocal
static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//新建两个线程
Thread t1 = new Thread(()->{
integerThreadLocal.set(1);
System.out.println(integerThreadLocal.get());
stringThreadLocal.set("aaa");
System.out.println(stringThreadLocal.get());
});
Thread t2 = new Thread(()->{
integerThreadLocal.set(2);
System.out.println(integerThreadLocal.get());
stringThreadLocal.set("bbb");
System.out.println(stringThreadLocal.get());
});
//启动看输出结果
t1.start();t2.start();
}
结果如下图所示:
这样就达到了同一个变量对不同的线程有不同的返回值的效果,小伙伴们有没有想一想,这个是怎么实现的呢?
ThreadLocal中的set()
最开始看这部分源码的时候,从set方法开始看,部分源码如下所示:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//实际上是使用了map的set
map.set(this, value);
} else {
createMap(t, value);
}
}
在这里面我们可以看到,我们使用set时,实际上是使用了ThreadLocalMap的set方法,我们点进去getMap(t)
/**
* @param t 当前线程
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
也就是说,这个Map实际上是当前线程里面的 threadLocals 字段。我们跟踪到Thead类中如下图所示,可以看到,这个threadLocals 是null , 在createMap时才创建,通过下面的代码可以看到,对这个threadLocals赋值是new ThreadLocalMap(this,firstValue) 和我们认识的HashMap等map类似,key 是 当前的threadLocal,value是当前的设置的值。
现在大家跟我一起回到第一段代码,在set的时候,如果获取到的map为空,则执行createMap方法。this 就是我们当前的threadLocal实例。根据上述,我们可以简单理一下:
一个Thread里面有一个ThreadLocalMap, 这个ThreadLocalMap里面存放着当前线程用到的ThreadLocal
ThreadLocal中的Get()
大致知道这个结构后,我们可以自己想一下get怎么实现的,可能是以下三步:
-
获取到当前线程
-
获取到当前线程中的ThreadLocalMap,也就是threadLocals变量
-
传入当前的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();
}
一看,果然也就是这几步,和我们想的大体是一致的,只不过比我们更加严谨,又加入了判空等操作。
可能有些小伙伴对传入当前ThreadLocal这块还不太理解,我们对使用篇的示例代码debug看下:小伙伴们可以自己debug下,有助于理解ThreadLocal变量实例的作用
总结
ThreadLocal是多线程这块入门级的知识点,本来对这块也没在意,最近工作用到了这个,就稍稍挖了一下,不挖不知道,一挖吓一跳。而且这还没有很深处写,再往深处的小伙伴可以看到Map里面的Entry是弱引用,既然说到了弱引用,那就要考虑到在YGC的时候是否被回收掉,虽然Entry是弱引用,但Entry的key 是ThreadLocal,我们是用new 来声明了一个强引用ThreadLocal,这时YGC时还会回收Entry吗?小伙伴们可以自己思考下