简介
ThreadLocal
并非是一个线程的本地实现版本,它并不是一个Thread
,而是threadlocalvariable
(线程局部变量)。
线程局部变量(ThreadLocal
)的功用就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal
实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
通过ThreadLocal
存取的数据,总是与当前线程相关,也就是说,JVM
为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal
是如何做到为每一个线程维护变量的副本的呢?
实现思路很简单,在ThreadLocal
类中有一个Map
,用于存储每一个线程的变量的副本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal
采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
内部结构
实现原理
从上面ThreadLocal
类图结构可知,Thread
类中有两个变量threadLocals
和inheritableThreadLocals
,二者都是ThreadLocal
内部类ThreadLocalMap
类型的变量。
通过查看内部类ThreadLocalMap
可以发现实际上它类似于一个HashMap
。在默认情况下,每个线程中的这两个变量都为null
,只有当线程第一次调用ThreadLocal
的set
或者get
方法的时候才会创建他们。
每个线程的本地变量不是存放在ThreadLocal
实例中,而是放在调用线程的ThreadLocals
变量里面,也就是说,ThreadLocal
类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get
方法时候能够从它的threadLocals
中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals
中,所以不使用本地变量的时候需要调用remove
方法将threadLocals
中删除不用的本地变量。
核心API
ThreadLocal
类提供如下几个核心方法:
1 | public T get() |
set方法
1 | public void set(T value) { |
如果调用
getMap
方法返回值不为null
,就直接将value
值设置到threadLocals
中(key
为当前线程引用,值为本地变量);
如果getMap
方法返回null
说明是第一次调用set
方法(前面说到过,threadLocals
默认值为null
,只有调用set
方法的时候才会创建map
),这个时候就需要调用createMap
方法创建threadLocals
;
1 | void createMap(Thread t, T firstValue) { |
createMap
方法不仅创建了threadLocals
,同时也将要添加的本地变量值添加到了threadLocals
中。
get方法
1 | public T get() { |
在
get
方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals
不为null
,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue
方法初始化threadLocals
变量。
在setInitialValue
方法中,类似于set
方法的实现,都是判断当前线程的threadLocals
变量是否为null
,是则添加本地变量(这个时候由于是初始化,所以添加的值为null
),否则创建threadLocals
变量,同样添加的值为null
。
remove方法
1 | public void remove() { |
remove
方法判断该当前线程对应的threadLocals
变量是否为null
,不为null
就直接删除当前线程中指定的threadLocals
变量。
ThreadLocalMap
ThreadLocalMap
是ThreadLocal
的内部类,没有实现Map
接口,用独立的方式实现了Map
的功能,其内部的Entry
也独立实现。
1 | /** |
从上面的代码可以看出,当前
ThreadLocal
的引用k
被传递给WeakReference
的构造函数,所以ThreadLocalMap
中的key
为ThreadLocal
的弱引用。当一个线程调用ThreadLocal
的set
方法设置变量的时候,当前线程的ThreadLocalMap
就会存放一个记录,这个记录的key
值为ThreadLocal
的弱引用,value就是通过set设置的值。如果当前线程一直存在且没有调用该ThreadLocal
的remove
方法,如果这个时候别的地方还有对ThreadLocal
的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal
变量的引用和value
对象的引用,是不会释放的,就会造成内存泄漏。
考虑这个
ThreadLocal
变量没有其他强依赖,如果当前线程还存在,由于线程的ThreadLocalMap
里面的key
是弱引用,所以当前线程的ThreadLocalMap
里面的ThreadLocal
变量的弱引用在gc
的时候就被回收,但是对应的value
还是存在的这就可能造成内存泄漏(因为这个时候ThreadLocalMap
会存在key
为null
但是value
不为null
的entry
项)。