ThreadLocal原理
阅读本文章之前,需要先了解Java中强软弱虚的概念,传送地址:Java中强软弱虚四种引用
文章目录
一、介绍
- 可以解决多线程的数据安全问题,将当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合等)
- 每个线程只能存取本线程的数据,无法操作其他线程的数据,各个线程的数据互相独立
- ThreadLocal中定义了
set、get、remove
方法,用来关联 / 取出 / 移除数据 - 每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal 对象实例
二、快速入门
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
//创建ThreadLocal对象实例
final ThreadLocal<Integer> th = new ThreadLocal<Integer>();
//t1线程
new Thread(new Runnable() {
@Override
public void run() {
try {
th.set(100); //t1线程存放数据
System.out.println("t1线程赋予的值:" + th.get());
Thread.sleep(2000); //t1线程睡眠2秒,睡眠的时间段内t2线程会运行
System.out.println("t1线程获取的值:"+th.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(1000);
//t2线程
new Thread(new Runnable() {
@Override
public void run() {
Integer ele = th.get(); //t2线程取出数据(此时为null,与t1无关系)
System.out.println("t2线程获取的值:" + ele);
th.set(200); //t2线程赋值
System.out.println("t2线程赋值后获取的值:"+th.get()); //t2线程从自己的副本中获取值
}
}).start();
}
}
运行结果:

三、ThreadLocal与synchronized的区别
- 原理不同
- synchronized
- 只提供了一份数据,让不同的线程竞争锁访问
- ThreadLocal
- 为每一个线程都提供了各自的数据,从而实现同时访问互不干扰
- synchronized
- 侧重点不同
- synchronized
- 多个线程之间访问资源的同步
- ThreadLocal
- 多线程中让每个线程之间的数据相互隔离
- synchronized
四、ThreadLocal内部结构
-
每个
Thread
维护一个ThreadLocalMap
,这个Map的key
是ThreadLocal
实例本身,value
是真正要存储的值 -
图示
Map由Entry键值对组成
当
Thread
销毁之后,对应的ThreadLocalMap
也会随之销毁,能减少内存的使用
五、ThreadLocal核心方法源码
1. set方法
-
源码和对应的中文注释
/** * 参数是Map中的value值 */ public void set(T value) { // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 判断map是否存在 if (map != null) // 存在则调用set方法,this表示此线程中TheadLocal实例 // 创建Entry并赋值 map.set(this, value); else // 如果当前线程不存在ThreadLocalMap对象,则创建Map createMap(t, value); } /** * 被调用的getMap方法 * @param 当前线程 * @return 对应维护的ThreadLocalMap */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * 被调用的createMap方法 * @param 当前线程 * @param 存放到Map中的第一个entry的值 */ void createMap(Thread t, T firstValue) { //this是ThreadLocal实例 t.threadLocals = new ThreadLocalMap(this, firstValue); }
-
代码执行流程
- 首先获取当前线程,并根据当前线程获取一个Map
- 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key),创建Entry并赋值
- 如果Map为空,则给该线程创建Map,并设置初始值,初始值即为参数值
2. get方法
-
源码和对应的中文注释
/** * 返回当前线程的Map中的value值 */ public T get() { // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 如果此map存在 if (map != null) { // 以当前的ThreadLocal为key,调用getEntry获取对应的Entry ThreadLocalMap.Entry e = map.getEntry(this); // 如果Entry不为空,返回对应的value值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //有两种情况执行setInitialValue()方法 //第一种情况: map不存在,表示此线程没有维护ThreadLocalMap对象 //第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry return setInitialValue(); } /** * 被调用的setInitialValue方法,创建Entry并返回对应的value值,默认为null */ private T setInitialValue() { // 调用initialValue获取初始化的值 // 此方法可以被子类重写, 如果不重写默认返回null T value = initialValue(); // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 判断map是否存在 if (map != null) // 存在则调用map.set设置此实体entry map.set(this, value); else // 1.当前线程Thread不存在ThreadLocalMap对象 // 2.则调用createMap进行ThreadLocalMap对象的初始化 // 3.并将t(当前线程)和value(默认为null)作为第一个entry存放至ThreadLocalMap中 createMap(t, value); // 返回设置的值value,默认为null return value; }
-
代码执行流程
- 首先获取当前线程, 根据当前线程获取一个Map
- 如果获取的 Map 不为空,则在Map中以 ThreadLocal 的引用作为key在Map中获取对应的
Entry e
- 如果 e 不为空,则返回
e.value
- 如果Map为空或者e为空,则通过
initialValue()
函数获取初始值 value(默认为null),然后用ThreadLocal的引用和value作为 firstKey 和 firstValue 创建一个新的 Map
-
总结: 先获取当前线程的 ThreadLocalMap 变量,如果存在则返回值,不存在则创建并返回初始值。
六、弱引用的使用
Entry对应的源码如下:
//Entry继承自一个弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
//属性value
Object value;
//构造器
Entry(ThreadLocal<?> k, Object v) {
super(k); //父类会创建一个弱引用,参数是ThreadLocal对象
value = v;
}
}
//被调用的父类方法
public WeakReference(T referent) {
super(referent);
}
//被调用的父类方法,第二个参数引用队列传递null值
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
通过上述源码得知,创建Entry的时候会创建一个弱引用,并指向当前线程的ThreadLocal对象,如下图所示:

1. 为什么要使用弱引用?
如果Entry中的key使用强引用指向ThreadLocal对象,当ThreadLocal实例 threadLocal = null;
时,由于ThreadLocal对象仍然被Entry中的key强引用,所以ThreadLocal对象不会被垃圾回收,存在内存泄漏。
内存泄漏:对象不会被GC所回收,然而它却占用内存。
如果使用弱引用,当垃圾回收时,ThreadLocal对象被回收,可以解决ThreadLocal对象内存泄漏的问题。
2. 使用弱引用后是否依然存在内存泄漏?
当ThreadLocal对象被回收后,Entry中的key为null,无法获取value的值,无法回收,所以value指向的内存空间仍然存在内存泄漏问题,故应该使用 remove()
方法将此条Entry记录删除。
3. 线程池归还线程必须清理Map
如果线程池中的线程归还时,没有清理ThreadLocalMap,则此线程被再次使用的时候,可能会导致Map中的数据发生错误,比如发现Map中已经有同名的key,则不会再插入新值等情况。