ThreadLocal.md
ThreadLocal 是一个线程变量,可以理解为一个线程的上下文对象。
ThreadLocal 使用
先来看看怎么使用,其实很简单:
static ThreadLocal<String> context = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
context.set("Thread 1 ");
printContext();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
context.remove();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
context.set("Thread 2 ");
printContext();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
context.remove();
}
}).start();
}
public static void printContext(){
System.out.println(context.get());
}
当我们调用 ThreadLocal#get()
的时候,会根据不同线程返回不同线程之前 set
的值。
其实可以简单理解为一个 HashaMap<Thread, Object>
,当每次调用 set()/get()
的时候,都以当前线程作为 key 对 value 进行操作。
源码分析
先从 set
开始:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
首先获取当前线程对象,然后调用 getMap
方法获取当前线程的 ThreadLocalMap
对象,这个刚刚类图中表示该类是 ThreadLocal
的一个静态内部类 。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
来到这里,我们可以看到 ThreadLocalMap
最终是在线程中获取的,线程中有一个 threadLocals 对象。
默认值为 null,当为 null 时,会来到 createMap
方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
至此, set 操作完,来看看 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();
}
还是获取当前线程中的 ThreadLocalMap
方法,然后调用其 getEntry(this)
方法,如果没有数据则会来到 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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
多是细节处理,主要为设置默认值,其中默认值在 initialValue();
中获取:
protected T initialValue() {
return null;
}
可以看到默认值为 null,这个方法使用的是 protected
,表示可以由子类重写 。实际上,其一个派生类 SuppliedThreadLocal
就是重写了这个方法,可以使用 Lamda 指定默认值。
ThreadLocalMap
接下来来到 ThreadLocalMap
,其实该类本质上是一个类似 HashMap<ThreadLocal, Object>
的数据结构,可以调用 set/get
方法写入或读取值,然后进行一些哈希处理。本质上是一个数组 实现的哈希表。关于哈希部分之后容器部分会说明。这里只需要了解。
至此,我们能理解 ThreadLocal
的大概工作流程, 每个 Thread 都有一个 ThreadLocalMap
的变量,每当调用 TrheadLocal#get
方法的时候,相当于对当前线程中 ThreadLocalMap
以当前操作的 ThreadLocal
为 key 操作数据。
弱引用
实际上,这里还有一点需要注意,我们来到 Map 中 Entry 类:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们注意到,该类继承于 WeakReference
,是一个弱引用,但是在构造方法里,传入 super 的是 k,也就是 ThreadLocal 对象,说明当外界失去 ThreadLocal 的引用后,该对象会在 GC 时被垃圾回收,从而避免了内存泄漏。
给个场景,例如在线程池中,某个线程在进行某个任务的时候使用了某个 ThreadLocal
,此时该线程中的 ThreadLocalMap
会通过 Entry 的 key 持有 该 ThreadLocal ,而线程完成任务后,没有调用 remove 方法,然后归还给线程池准备下一次工作,此时便会一直持有该 ThreadLocal
对象。
而因为使用了弱引用,在下次 GC 时,就会回收。 而因为 Key 为 Null,此时散列表会判断该位置可以存放数据,因此 Object 则会在该位置被覆盖后失去引用被 GC。
虽然使用了弱引用防止内存泄漏,但作为好习惯,还是需要记得在 ThreadLocal
使用完后 remove
;