线程数据ThreadLocal
About 2 min
如果想在线程中保存些数据,
ThreadLocal的使用
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
var threadLocal = new ThreadLocal<>() {
@Override
protected String initialValue() {
return "huhx";
}
};
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
threadLocal.set("linux");
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
}).start();
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
}
}
java 8提供了withInitial
的静态方法来创建和初始化ThreadLocal
。所以上述ThreadLocal的创建可以修改为:
var threadLocal = ThreadLocal.withInitial(() -> "huhx");
上述代码输出结果:
main: huhx
Thread-0: huhx
Thread-0: linux
main: huhx
ThreadLocal的分析
第个线程存放的数据会有所差别,如何把线程的数据加以隔离?
- ThreadLocal的初始化,
ThreadLocal.withInitial
,这是从java 8才开始有的
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
- ThreadLocal的
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);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
在set
时,首先获得当前线程对象,然后通过getMap
拿到线程的ThreadLocalMap
,并将值设入ThreadLocalMap
中。而ThreadLocalMap可以理解为一个Map,注意ThreadLocalMap
是Thread类的成员变量。ThreadLocalMap
的key是ThreadLocal当前对象,value是set
方法的参数。
- 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();
}
get
方法先取得当前线程的ThreadLocalMap
对象。然后通过将自己作为key
取得内部的实际数据。如果找不到ThreadLocalMap,那么设置并返回ThreadLocal的初始值。
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;
}
TerminatingThreadLocal这个特殊的ThreadLocal,能够在线程终止时收到通知。
FAQ
为什么Entry要使用弱引用?
若是强引用,即使t1=null, 但key的引用仍然指向ThreadLocal
对象,所以会有内存泄露风险,而强引用则不会。
但还是有内存泄露存在,ThreadLocal
被回收,key的值变成null,导致对应的value值再也不能被访问到,因为仍旧会出现内存泄露。