从源码开始重新认识ThreadLocal
最近在巩固Java基础,发现很多平时在使用的东西,其实自己并不了解它的原理,在看了JDK1.8中ThreadLocal这个工具类的源码的同时,也翻看了很多大牛写的博客,总结下来,加深记忆。
简介
从JDK1.2开始,Java就提供了ThreadLocal类。
所谓ThreadLocal,是Thread Local Variable(线程局部变量)的意思,ThreadLocal是java.lang包下提供的一个工具类,主要的作用是隔离线程资源,保证线程安全,通过ThreadLocal类,我们可以为每个线程创建一个独立的变量副本,从而避免并发访问时的线程安全问题。
基本方法
ThreadLocal类似于HashMap,保存的是k:v型数据结构,但是他只能保存一个,各个线程的数据互不影响。
ThreadLocal只提供了一个空的构造函数。1
2
3
4
5
6/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
ThreadLocal中的get()方法,不用传入任何参数
1 | public T get(); |
ThreadLocal的set()方法,放入的是一个泛型参数
1 | public void set(T value); |
ThreadLocal的remove()方法
1 | public void remove(); |
针对ThreadLocal的主要使用就是这三个方法,所以说ThreadLocal的使用其实并没有任何难度,不需要写任何同步代码就可以实现线程安全。
示例
1 | public class ThreadLocalExample { |
输出结果
1 | 当前线程:线程1 |
可以看出线程1和线程2的变量完全隔离开了。
从源码看原理
那ThreadLocal是如何做到这些的呢,先来看看set方法的源码。
1 | public void set(T value) { |
重点就在于这个ThreadLocalMap,ThreadLocal就是通过这玩意来实现线程隔离的。下面是getMap方法:
1 | ThreadLocalMap getMap(Thread t) { |
这里返回的是t对象也就是当前线程对象里面的threadLocals这个变量。我们再看看Thread源码:1
2
3
4/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
看注释的意思是:threadLocals是用于修饰当前线程的ThreadLocal值,这个ThreadLocalMap变量由ThreadLocal来维护。
看到这里明白了,ThreadLocal之所以能够隔离线程资源,是因为每个线程的ThreadLocalMap都在当前线程对象里,其他线程根本无法访问到。
继续看set方法的源码,获取到ThreadLocalMap对象后,开始设置值。其中有两个操作:map.set(this, value)和createMap(t, value),第一个是调用ThreadLocalMap的set方法,此处注意:传入的key是当前ThreadLocal对象,createMap方法是调用了ThreadLocalMap的构造方法,同样传入的key也是当前ThreadLocal对象,此处不贴代码了。
get()方法的源码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//拿到ThreadLocalMap中的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
"unchecked") (
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
从代码可以看出get方法要返回的值是ThreadLocalMap中的Entry对象的value值。
ThreadLocalMap
从上面的分析中,已经认识到了ThreadLocalMap这个类的重要性,ThreadLocalMap是ThreadLocal的一个静态内部类,从命名来看,这也是一个map结构,没错,其实ThreadLocal中很多东西都和HashMap中的很像,接下来继续看ThreadLocalMap的源码。
调用ThreadLocalMap的构造方法,会初始化一个长度为16的Entry数组,每一个
Entry对象保存的都是k-v键值对,key是ThreadLocal,调用ThreadLocal的set方法,相当于是把他自己当成key放进ThreadLocalMap中。
1 | /** |
再看看Entry:
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |
Entry继承了WeakReference这个类,并把key保存在了WeakReference中,这代表了Entry的key是一个弱引用,这会导致k也就是ThreadLocal对象在没有外部强引用指向它的时候,他会被gc强制回收。
ThreadLocalMap的set方法,ThreadLocal的set方法也是调用的这个方法。
1 | /** |
set的基本过程是:
- 根据key(ThreadLocal)的hashcode计算出Entry的位置,每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647。
- 然后和计算出的Entry的key进行比较,如果相等,那么就放入新值
- 如果计算出的Entry的k为空,说明已经被gc,就替换过期的Entry值
- 如果都没有满足,说明计算出的Entry的key和当前要设置的值没有任何关系,初始化一个新的Entry放入当前的位置
ThreadLocal的内存泄漏
前面说过,Entry的key是个弱引用,如果被jvm的gc回收,那么就会出现一个问题,Entry的value在当前线程一直运行的情况下,Thread中持有ThreadLocalMap对象,相当于持有对Entry对象的强引用,如果线程不停止,Entry的value可能一直得不到回收,时间长了,就会发生内存泄漏。解决的办法是在使用了ThreadLocal的set方法后,显式的调用ThreadLocal的remove方法。
总结
这是一张手画的ThreadLocal的基本原理图
总结下来就是:每个Thread维护一个ThreadLocalMap映射表,这个map的key是ThreadLocal实例本身,value是真正需要存储的Object。ThreadLocal本身并不存储值,它只是作为一个key来让线程从map中获取value,虚线标识弱引用,表示ThreadLocalMap是使用ThreadLocal的弱引用作为key,弱引用在GC时会被回收。
源码看起来虽然很痛苦,但是却能学到很多东西,以前的自己很少去注意这些,只会使用,这样对于一个Java程序员修炼内功是极为不利的,如果有不对的地方,欢迎指出。
持续学习,夯实基础,共勉。
感谢
以下的博客给了我很多帮助
占小狼,狼哥的博客给了我很多帮助
https://www.jianshu.com/p/377bb840802f
@kiraSally