源码解读:ThreadLocal

100 阅读2分钟

ThreadLocal介绍

线程本地变量,修饰的变量在线程间独立,互不影响。

ThreadLocal<String> local = new ThreadLocal<>();
local.set("caojiantao");
new Thread(() -> {
    local.set("chenlisha");
}).start();
new Thread(() -> {
    // null
    System.out.println(local.get());
}).start();

通常有以下几个应用场景:

  • 保存请求用户态(拦截器)
  • 动态数据源读写分离(AbstractRoutingDataSource)
  • 数据库连接(事务性)

原理图解

着重描述下ThreadLocalMap

  • Thread的私有变量,线程间隔离
  • 未实现Map接口,JDK自实现的简易Map
  • Entry继承自WeakReference,对应的Key是弱引用
  • KeyThreadLocal实例
  • 通过开放寻址法解决hash冲突

为什么是弱引用

弱引用:只具有弱引用的对象在GC时会被回收。

总结:弱应用+实时清除,避免弱应用问题。

  1. ThreadLocal是弱引用,在GC没有其他引用时内存会被回收;
  2. ThreadLocalgetset时都会清除keynullEntry

如果不是弱引用,当ThreadLocal不可触达时,关联的value也无法触达,刚好线程生命周期长(线程池)的话,由于ThreadLocalMap强引用的原因,value一直不会被释放从而造成内存泄露。

ThreadLocal.set

public void set(T value) {
    // 获取当前线程关联的ThreadLocalMap
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 如果为空需要初始化
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal.get

public T get() {
    // 获取当前线程关联的ThreadLocalMap
    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();
}

ThreadLocal.remove

public void remove() {
    // 获取当前线程关联的ThreadLocalMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        // 清除当前key(ThreadLocal)和value(Object)
        m.remove(this);
}

为了避免内存泄漏,一定要触发remove方法。

ThreadLocalMap

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
        // ThreadLocal关联的对象
        Object value;

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

    // 开放寻址法,没有链表
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    // ...
    
    private void rehash() {
        // 删除空的entry节点
        expungeStaleEntries();
        if (size >= threshold - threshold / 4)
            // 2倍扩容
            resize();
    }
}

补充:InheritableThreadLocal

Inheritable,可继承的,意为可继承的ThreadLocal。通常用来父子线程传递参数,例如请求userId。

InheritableThreadLocal<String> inheritable = new InheritableThreadLocal<>();
inheritable.set("caojiantao");
new Thread(() -> {
    // caojiantao
    System.out.println(inheritable.get());
}).start();

看看InheritableThreadLocal的源码(部分);

// 继承自ThreadLocal并重写了两个方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    // InheritableThreadLocal也是Thread的一个私有变量
    @Override
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    // 实例初始化
    @Override
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

如何传递参数给子线程,那就看看Thread的init方法(部分);

private void init(... boolean inheritThreadLocals) {
    ...
    Thread parent = currentThread();
    // inherit默认为true
    if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
        // 默认将父线程的inheritable浅拷贝了一份到子线程中
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    }
    ...
}

值得注意的是,线程池复用的线程不会重复触发Thread的init方法,会导致参数丢失。