Skip to main content

线程数据ThreadLocal

huhxAbout 2 minjavaThreadConcurrency

如果想在线程中保存些数据,

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值再也不能被访问到,因为仍旧会出现内存泄露。

总结

参考