ThreadLocal
作用
有一个比喻:
学生需要在签字墙签字,锁 相当于只有一个签字笔,学生们需要争抢这个签字笔;而 ThreadLocal
相当于给每个学生发了一个签字笔,每人一个,效率大大提升。
底层原理
假如你自己需要实现 ThreadLocal<T>
相关的 API,请问你会怎么做?
- 你可能会使用
ConcurrentHashMap<Thread, T>
,以Thread.currentThread()
作为 key,这完全可以。但是缺点明显:1. 处理并发问题;2. 必须有指针指向 Thraed 和 这个对象,即使 Thraed 已经结束了,可以被 GC 了。 - 那我们改为
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
怎样? 可以解决 GC 问题,但是多线程问题仍然没有解决。
Java 实现的想法,没有用 <Thread, T>
,而是大概如下:
new WeakHashMap<ThreadLocal,T>()
而事实上,在每个 Thread
内部也的确有这么一个 Map
指针:
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
虽然 ThreadLocalMap
并不是一个 WeakHashMap
,但是它的设计类似 WeakHashMap
,它的 Key 是由 Weak Reference 引用的。
再看 ThreadLocal.get()
方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocal.setInitialValue()
的实现:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
假设你定义了如下几个 ThreadLocal
:
ThreadLocal<SimpleDateFormat> threadLocalA = new ThreadLocal<>();
ThreadLocal<Random> threadLocalB = new ThreadLocal<>();
ThreadLocal<Buffer> threadLocalC = new ThreadLocal<>();
那么 Thread.currentThread()
指向了一个 ThreadLocalMap
,这个 Map
存储了 3 个 ThreadLocal
:
使用场景
- 你想要在多线程环境下访问某个非线程安全的对象(例如
SimpleDateFormat
),但是你又想避免通过添加synchronized
来进行同步,此刻可以考虑使用ThreadLocal
来给每个线程都搞一份找个对象的实例。 - 链路追踪、RPC 传递链路消息 等:很多框架使用
ThreadLocal
来存储与当前线程相关的上下文信息Context
,这样从 A 方法调用到 B 方法的时候,不用手动通过参数传入当前的Context
信息,直接从ThreadLocal
中读取即可。 - 多数据源切换
内存泄露
ThreadLocal
已经被赋值为 null
了,但是由于 Thread
没有运行完,一直强引用着 ThreadLocalMap
,那么这个 Map
无法被 GC,导致这个 Map
所引用的 value
无法被回收(key
是可以被回收的,因为 key
是弱引用),从而出现内存泄露。
ThreadLocalMap
中的每一项如下所示,即 key
是 WeakReference
,而 v
也就是 value
是强引用:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
为什么 value
不也弄成弱引用? 如果弄成弱引用,那么每次 GC 都有可能被清空,那么就无法自始至终地保存这一个 value
对象了,每次获取到的有可能都是一个全新的对象。
推荐:每次使用完 ThreadLocal
,都调用 ThreadLocal.remove()
方法清除数据。
objectThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}
InheritableThreadLocal
父线程传递 value
给子线程,当子线程创建的时候,子线程会收到从父线程继承来的所有的 thread-local 的变量。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
}