温州哪里做网站比较好/站长统计app软件下载官网
ThreadLocal原理
阅读本文章之前,需要先了解Java中强软弱虚的概念,传送地址:Java中强软弱虚四种引用
文章目录
- ThreadLocal原理
- 一、介绍
- 二、快速入门
- 三、ThreadLocal与synchronized的区别
- 四、ThreadLocal内部结构
- 五、ThreadLocal核心方法源码
- 1. set方法
- 2. get方法
- 六、弱引用的使用
- 1. 为什么要使用弱引用?
- 2. 使用弱引用后是否依然存在内存泄漏?
- 3. 线程池归还线程必须清理Map
一、介绍
- 可以解决多线程的数据安全问题,将当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合等)
- 每个线程只能存取本线程的数据,无法操作其他线程的数据,各个线程的数据互相独立
- 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() {@Overridepublic 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() {@Overridepublic 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对象,则创建MapcreateMap(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获取对应的EntryThreadLocalMap.Entry e = map.getEntry(this);// 如果Entry不为空,返回对应的value值if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//有两种情况执行setInitialValue()方法//第一种情况: map不存在,表示此线程没有维护ThreadLocalMap对象//第二种情况: map存在, 但是没有与当前ThreadLocal关联的entryreturn setInitialValue(); }/*** 被调用的setInitialValue方法,创建Entry并返回对应的value值,默认为null*/ private T setInitialValue() {// 调用initialValue获取初始化的值// 此方法可以被子类重写, 如果不重写默认返回nullT value = initialValue();// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 判断map是否存在if (map != null)// 存在则调用map.set设置此实体entrymap.set(this, value);else// 1.当前线程Thread不存在ThreadLocalMap对象// 2.则调用createMap进行ThreadLocalMap对象的初始化// 3.并将t(当前线程)和value(默认为null)作为第一个entry存放至ThreadLocalMap中createMap(t, value);// 返回设置的值value,默认为nullreturn 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<?>> {//属性valueObject 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,则不会再插入新值等情况。