JDK1.8并发之ThreadLocal源码解析

本文的目的是分析 ThreadLocal 的源码,关于 ThreadLocal 如何使用,请阅读参考资料1。

ThreadLocal.ThreadLocalMap

每个线程对象都有一个 ThreadLocalMap 类型的变量。ThreadLocalMap 是 ThreadLocal 的内部类,是用基于线性探测法的散列表实现的。每一个线程对象可以往 Map 中添加多个 ThreadLocal 对象为键的键值对,每个键对应的值唯一。所以通过一个 ThreadLocal 对象设置的值,在每个线程中都是唯一且互相独立的。唯一是因为键的唯一性,独立是因为每个线程都有自己的ThreadLocalMap内部变量,它是归线程所有的。

// java.lang.ThreadLocal.ThreadLocalMap

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

有趣的是它的元素是弱引用,被弱引用关联着的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被引用关联的对象。因此如果某个线程已经运行结束,不会因为这里有对它的引用而就无法回收,这个键会在回收后变成null。

所以在 ThreadLocalMapset 方法中会调用 rehash 方法,rehash 方法先清理这些已经变成null的弱引用,对后面的部分Entry重新进行哈希散列,最后再判断是否扩容。(推荐阅读参考资料3)

static class Entry extends WeakReference> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocal 构造器

public ThreadLocal() {
}

构造器为空。

ThreadLocal 的 set 方法

为当前线程的 ThreadLocalMap 对象添加键值对,键为 ThreadLocal 对象,值为设置的 value 对象。

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 已经调用过set或get方法时,往线程的map中添加新的值,键为当前ThreadLocal对象
    if (map != null)
        map.set(this, value);
    // 没有调用过set或get方法时,为当前线程创建一个ThreadLocalMap
    // 并添加第一个值,键为当前ThreadLocal对象
    else
        createMap(t, value);
}

// 返回Thread对象t的ThreadLocalMap属性,初始为null
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal 的 get 方法

线程中调用 get 方法时,如果之前用 set 方法为这个 ThreadLocal 键添加过值,那么就返回设置过的值,否则就返回默认初始值 null。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 已经调用过set或get方法时,对象值已经设置过,就返回上一次的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 没有调用过set或get方法时,对象值没有设置过,就自动设置初始值,并返回初始值
    return setInitialValue();
}

private T setInitialValue() {
    // 获得初始值
    T value = initialValue();
    // set方法
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

ThreadLocal 的 initialValue

通常可以在新建 ThreadLocal 对象时,采用匿名内部类的方式重写该方法,来设置共享的初始值。详细见参考资料1。

// 这是一个可重写的方法,用来设置ThreadLocal对象在所有线程中的初始值
protected T initialValue() {
    return null;
}

InheritableThreadLocal

使用 InheritableThreadLocal 可以在子线程中获取到父线程在new该线程之前,保存在 InheritableThreadLocal 变量中的对象值。

public class Main {
    private static InheritableThreadLocal local =
            new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        local.set(1001);
        new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +
                    ":" + local.get());
        }).start();
        Thread.sleep(1000);
        local.set(2002);
        System.out.println(Thread.currentThread().getName() +
                ":" + local.get());
    }

}

/* 输出为:
    main:2002
    Thread-0:1001
*/

这是因为在每个线程在构造的时候,会复制父线程对象的 inheritableThreadLocals 属性。父线程的 inheritableThreadLocals 对象不为空时,就会复制一份到子线程,复制的是对象引用(不是克隆),之后父线程对 inheritableThreadLocal 变量 set 新的值不再影响子线程,但如果父线程修改值对象在堆中的字段的话,子线程中的值也会变化。

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

// inheritThreadLocals = true
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    ...
    // 父线程对象
    Thread parent = currentThread();
    ...
    // 父线程的 inheritableThreadLocals 对象不为空时,就会复制一份到子线程。
    // 复制的是对象引用,之后父线程对 inheritableThreadLocal 变量 set 新的
    // 值不再影响子线程,但如果父线程修改对象在堆中的值的话,子线程中的值也会
    // 变化
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ...
}

ThreadLocal 使用场景

  1. 某些数据以线程为作用域并且不同线程具有不同的数据副本是。Android 消息机制中,每个线程都要有一个自己的 Looper,通过 ThreadLocal 可以轻松实现 Looper 在线程中的存取。
  2. 复杂逻辑下的对象传递。比如任务复杂时函数调用栈比较深,而我们又需要监听器能贯穿整个线程执行过程,可以用 ThreadLocal 让监听器成为线程内的全局对象。


参考资料

  1. ThreadLocal的设计理念与作用
  2. Java中的ThreadLocal和 InheritableThreadLocal
  3. ThreadLocal原理解析(2):ThreadLocalMap源码解析
  4. Java中强、软、弱、虚引用

你可能感兴趣的:(Java)