ThreadLocal是什么?
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread不可访问,那就不存在多线程间共享的问题。
ThreadLocal实现原理
set方法的实现
在使用ThreadLocal的时候,用的最多的就是get和set两个方法。我们就先从set入手来分析ThreadLocal的实现原理。
1 | public void set(T value) { |
这个方法中我们首先需要注意的一点就是 getMap(t)方法。
它的实现就一行代码return t.threadLocals;就是获取当前Thread对象的一个threadLocal属性。
在Thread类中有这么一个属性:
1 | ThreadLocal.ThreadLocalMap threadLocals = null; |
这里的 ThreadLocal.ThreadLocalMap就是ThreadLocal中的一个静态内部类。
从这里我们也可以看出,ThreadLocal每个线程的变量副本并不是存在ThreadLocal中的,而是存在线程对象(Thread)中的。
尝试直接从当前Thread对象中直接获取失败之后,就会执行createMap(t, value);方法,这个方法的具体实现如下:
1 | void createMap(Thread t, T firstValue) { |
这里再一次印证了我们的结论。
现在我们基本清楚了ThreadLocal是如何为每个Thread维护变量副本的了,ThreadLocal实际上只充当了中间人的角色,实际上变量副本的存储是由每个线程本身完成的。
get方法的实现
下面我们来分析一下get方法的实现。
1 | public T get() { |
通过代码不难看出,get方法就是简简单单的从Thread对象中取值,如果取到了就返回,没取到就返回初始值。
ThreadLocalMap源码分析
通过对set、get方法的源码分析,我们可以清楚的知道,每个线程的变量副本实际上是由线程对象本身通过一个ThreadLocalMap对象存储的。而ThreadLocalMap是ThreadLocal中的一个静态内部类。想要搞清楚整个原理,这个ThreadLocalMap的实现就是绕不开的。那么下面,我们来看一下它的内部实现。
TreadLocalMap本质上也是一个Map,那么我们首先看一下它的节点类。
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |
如果熟悉HashMap源码的同学可能就已经看出不同来了,这个Entry继承自WeakReference。这里的WeakReference<ThreadLocal<?>>,ThreadLocal就是弱引用对象,如果ThreadLocal对象没有被任何强引用指向时,一旦发生GC无论内存空间是否足够,都会被回收。
既然是Map,那么我们就从set和get方法来入手来分析一下这个Map和我们熟悉的HashMap等有什么区别。
下面我们就来看一个set方法的具体实现;
1 | private void set(ThreadLocal<?> key, Object value) { |
rehash是每个Map必须要分析的方法,那么我们看一下ThreadLocalMap是如何rehash的。
1 | private void rehash() { |
下面我们重点看一下,resieze方法
1 | private void resize() { |
这段代码的逻辑还是比较清晰的。新建一个长度为原先两倍的Entry数组,然后把旧数组中的数据重新计算索引后放入新的Entry数组。
看完了ThreadLocalMap是如何存的,下面我们就来分析一下,ThreadLocalmap是如何取得。
1 |
|
取到了直接返回,没取了就调用了getEntryAfterMiss方法。
1 | private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { |
到此,我们清楚了ThreadLocalMap是如何工作的了。
ThreadLocal可能引起的内存泄露
刚才我们分析了ThreadLocalMap,我们知道这个Map的key是一个弱引用对象,当没有强引用指向这个作为key的弱引用对象之后,它会被下一次gc回收掉,失去了key,我们就无法再访问Map中的value了,但是value却不会被回收,因为存在一条current thread连接过来的强引用,只有当前thread结束以后,强引用才会断开,这个时候Current Thread,Map,value才会被GC回收。

在之前的源码分析的时候,我们也看到了在get,set等方法中都清除了ThreadLocalMap中所有key为null的value。一定程度上缓解了内存泄漏的风险,但是这些被动的预防措施不能确保不发生内存泄漏。
而且很多时候使用static修饰ThreadLocal延长ThreadLocal的生命周期,可能导致内存泄漏。但是我们也不能说用static修饰ThreadLocal就不好,其实用static修饰还是有好处的,它可以避免重复创建等同的对象。
我们在使用的时候,一定要注意调用remove方法。