起因
为什么要写这篇文章?作为一个java程序员,你会发现在很多项目中都会使用这个工具,不仅如此,ThreadLocal还是面试经常被问到的一个考点,由此可见ThreadLocal的重要性。我自己也在学习的过程中使用过ThreadLocal,但只停留在别人教我这里要使用,我就使用的层面,对于为什么要使用ThreadLocal以及它的工作原理都是懵懵懂懂的状态。因此想要借此文章系统整理一下关于ThreadLocal的知识点,从而加深对它的理解
问题:ThreadLocal有什么用?
当你看到别人使用ThreadLocal时,你可能会问,为啥要使用ThreadLocal呢?因此,我们第一步需要知道ThreadLocal有什么用,它能解决什么问题。
ThreadLocal最主要的用途就是用来保证线程间数据的相互隔离。什么意思呢?简单来说就是在多线程环境下,每个线程从ThreadLocal中获取的数据都是自己之前存放进去的数据,而不会是其他线程的数据。基于此特性,在很多场景下都能使用ThreadLocal,比如层与层间进行参数传递,这样只要在一层中向ThreadLocal中存入数据,在其他层就可以直接获取数据,而不用在方法中显式传参。其实我们大部分的使用场景都是进行参数传递。
问题:ThreadLocal是如何工作的?
在了解了ThreadLocal的使用场景后,我们继续深入源码来了解ThreadLocal的工作原理,学习人家是如何保证线程间的数据隔离的。这里我们着重关注的是ThreadLocal的set()和get()方法(当然从中会牵扯出其他相关的方法)。
java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
通过以上代码可以大概知道的是,set方法的底层会先获取当前线程,接着通过同类方法getMap获取一个ThreadLocalMap对象,最后以ThreadLocal对象为Key,要存放的数据为Value存放到ThreadLocalMap中。听完你可能脑袋里会有点晕晕的感觉,别急,我们接着往下看。首先我们要解决的问题是ThreadLocalMap是个什么东西?
我们先来看看ThreadLocal中的源码
java
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
我们再来看看getMap()的源码
java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
通过以上源码大概可以知道以下信息:ThreadLocalMap是ThreadLocal中的一个静态内部类,并且每个线程中都有一个ThreadLocalMap属性。
以下是Thread中的源码
java
ThreadLocal.ThreadLocalMap threadLocals = null;
至此,我们可以简单梳理一下调用set方法的整个过程,用一个流程图来表示
你可能还会有疑问:为什么不直接用一个Map,在这个Map中Key存每个线程,Value存储每个线程对应的值呢?
这样做也能做到线程间数据的隔离,但是这种做法有一个问题:每个线程最多存放一个值,后面存放的值都会被覆盖。而jdk中实现的ThreadLocal,每个线程都拥有一个ThreadLocalMap,因此每个线程可以存放多个值,只需要new 一个对应的ThreadLocal即可
java
ThreadLocal<User>tl1 = new ThreadLocal<>();
ThreadLocal<IdCard> tl2 = new ThreadLocal<>();
tl1.set(new User());
tl2.set(new IdCard());
我们再来研究get方法,先来看下get方法的源码
java
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();
}
在有了上面的基础后,相信大家能够轻易的读懂这部分源码。一个很简单的逻辑:取出线程对应的ThreadLocalMap,再通过当前ThreadLocal取出对应的value,如果不存在map或Entry就返回一个默认值。这里有意思的是第5行代码,通过获取this对应的Entry来防止空指针异常。
ThreadLocal中存在的问题
很多时候如果我们不正确的使用ThreadLocal可能会造成严重的后果,最为熟知的就是内存泄露问题。
这是因为ThreadLocalMap中的key是弱引用(请看上述ThreadLocalMap源码),当外部不存在强引用时,就会被垃圾回收机制自动回收,但是value是一个强引用,只有当前线程结束运行后才会被回收,如果这些线程不结束运行的话,那么这些value就会一直存在,最后导致OOM。
虽然ThreadLocal中在进行set,get方法时底层都会调用expungeStaleEntry()来进行清理,但是在某些情况下依然会发生内存泄露,因此,一个良好的习惯依然是:当你不需要这个ThreadLocal变量时,主动调用remove(),这样对整个系统是有好处的。
关于ThreadLocal的一些其他知识点
还有一些关于ThreadLocal的知识点也是需要我们了解的,比如ThreadLocalMap是通过开放寻址法来解决哈希冲突的;在ThreadLocal中还提供了一个可被继承的ThreadLocal------InheritableThreadLocal,支持父子线程间共享数据,有兴趣的朋友可以阅读以下文章,由于笔者实力有限,只能写到这里(后续可能会去研究一下)。
参考文章: