ThreadLocal.md

何言 2021年08月11日 61次浏览

ThreadLocal 是一个线程变量,可以理解为一个线程的上下文对象。

image20210728150148044.png

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