并发编程 ThreadLocal
本文将介绍并发编程中的 ThreadLocal 及线程存储模式。
一、什么是线程存储模式?
如果把共享数据的可见范围限制在线程以内,就可以避免并发问题,线程存储模式就是这样一个模式。
二、线程存储方法
1. 简单做法
在 ThreadLocal 的内部持有一个 Map,它的 key 是线程,value 是变量。保存与获取变量时,根据线程找到对应的 value。
2. ThreadLocal 的做法
将线程变量缓存在线程中。Thread 对象中持有一个 ThreadLocalMap,其 key 为 ThreadLocal,其值为具体的线程变量值。
实际拿取时,ThreadLocal 会先获取到代表当前线程的 Thread 对象,然后以自身作为 key 获取 value。具体的拿取细节都已经被 ThreadLocal 隐藏,实际使用时我们只需要实例化 ThreadLocal、调用其 get()
、set()
方法即可。
下图是 ThreadLocal 的 get()
方法源码:
3. ThreadLocal 的做法的好处
线程的数据本身就属于线程,因此将数据放置到 Thread 对象中更符合逻辑
在 “简单做法” 中,ThreadLocal 内部的 Map 持有 Thread 对象的引用,ThreadLocal 的存活时间往往较长,这将影响 Thread 对象的回收
线程 A 已经执行结束,理应回收,但由于 ThreadLocal 持有所有线程的引用,因此得等待 ThreadLocal 回收后线程 A 才有可能回收
三、ThreadLocal
ThreadLocal 用于设置线程变量。
注意:ThreadLocal 用于设置而非存储线程变量,线程变量存储在 Thread 中
线程变量在每个线程中都有单独的副本,使用线程变量可以解决并发访问问题。
四、ThreadLocal 的简单使用
在并发场景中,利用 ThreadLocal 使用线程不安全的 SimpleDateFormat 的示例:
1 |
|
五、ThreadLocal 的存储结构
- Thread 对象中持有一个 ThreadLocalMap
- ThreadLocalMap 中维护了一个 Entry 数组,它是一个哈希表
- Entry 即是 Thread 对线程变量键值对的存储,它继承了 WeakReference,key 是对 threadLocal 的弱引用,value 是线程变量
六、内存泄露问题
1. 说明
使用 ThreadLocal 可能导致内存泄漏。
原因在于:
- 线程往往存活时间很长
- 线程对象持有的 ThreadLocalMap 将和线程同生共死
- ThreadLocalMap 持有的 Entry 将与 ThreadLocalMap 同生共死
- Entry 对 key 是弱引用,当 key 不再被其它其它地方引用时,key 可以被回收
- Entry 对 value 是强引用,value 将与 Entry 同生共死
因此,线程变量值往往会一直存在且无法被回收。
2. 解决办法
通过手动释放解决。
1 |
|
七、ThreadLocal 的继承性
如果在线程中创建子线程,子线程无法继承父线程的线程变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
static final ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
System.out.println(threadLocal.get());
new Thread(() -> {
System.out.println(threadLocal.get());
}).start();
}
}
JUC 提供了 InheritableThreadLocal,它允许子线程继承父线程的线程变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test1 {
static final InheritableThreadLocal<SimpleDateFormat> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
System.out.println(threadLocal.get());
new Thread(() -> {
System.out.println(threadLocal.get());
}).start();
}
}
参考
- Java 并发编程实战