菜鸟工程师-ThreadLocal源码学习

136 阅读4分钟

ThreadLocal是开发中非常实用的工具,比如用户个人信息等数据可以存储在ThreadLocal中,今天来解析一下源码,至于Java虚拟机层面,ThreadLocal数据的实际内存存储等待后续学习更新。

资料:从一个ThreadLocal引发的考点

initialValue()

    protected T initialValue() {
        return null;
    }

initValue()是一个初始化方法,开发者可以直接进行Override,想初始化什么数据都可以在这个方法中实现,该方法仅仅只是返回ThreadLocalMap中的value值。

withInitial()

    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();
        }
    }

withInitial()其实也是一个初始化方法,入参必须继承ThreadLocal指定的类,SuppliedThreadLocalThreadLocal的子类,暂时没发现什么特殊的点。withInitial()用法如下:

    private static final ThreadLocal<Map<Object, Object>> threadLocal = ThreadLocal.withInitial(HashMap::new);

get()

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // ThreadLocalMap存储在线程中,get的时候需要从当前线程 t 拿数据
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); // this是当前的ThreadLocal对象,这边就是简单的从map中,根据key取值,开发者可以创建多个ThreadLocal
            if (e != null) {
                // 返回数据
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 如果当前ThreadLocalMap还是null,则需要给当前线程初始化一下,看setInitialValue()的解析
        return setInitialValue();
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

get()方法首先从当前线程中拿到ThreadLocalMap,再从ThreadLocalMap中拿到开发者指定的ThreadLocal的value。这边可能会有点绕,总结起来其实就是以下三点:

  • ThreadLocalMap是Thread的一个属性,即这个东西跟着线程走的,取的时候自然要从当前线程拿
  • ThreadLocalMap是一个Entrt[]数组,Entry的中是ThreadLocal和存储的数据的键值对
  • 创建的每个ThreadLocal对象,都是存储在ThreadLocalMap

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);
    }

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

这边的操作与get()方法类似,都是要从当前线程中拿到ThreadLocalMap,set当前<ThreadLocal, value>。如果没有ThreadLocalMap,自然要先创建一个ThreadLocalMap,同时用当前set的<ThreadLocal, value>来进行ThreadLocalMap的初始化。

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);
        return value;
    }

set()方法类似,区别就在于,set的value是外界提供的,还是ThreadLocal自身的initialValue()方法,不过initialValue()也可以Override,根据用户的需求进行初始化。

remove()

    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            m.remove(this);
    }

remove()方法,是将当前的ThreadLocal,从ThreadLocalMap中移除,如果是使用线程池的Thread,这个方法很重要,举个例子

  • A用户登陆系统进行操作,后台分配了个Thread-1,A的信息在Thread-1的ThreadLocal中存储着,A走完了业务流程,Thread-1带着存储A的信息的ThreadLocal回到了线程池;
  • B用户登陆系统进行操作,这时候后台同样分配了Thread-1用于处理用户B的操作,这时候有可能会读取Thread-1中存储的用户A的ThreadLocal数据。

ThreadLocal在JVM中的实现

上文说过,ThreadLocalMap是存储ThreadLocal变量的地方,ThreadLocalMap内部是一个Entry[]数组:

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    // 下面的代码先不管
    ....
}

ThreadLocalMap并不是如我们熟知的HashMap一样,直接继承了AbstractMap,而是一个全新的类,ThreadLocalMap.Entry自然也是不同的,只不过他们都是使用哈希算法计算key的index,并存储到Entry[]数组中。ThreadLocalMap.Entry的定义并不是Map.Entry<K,V>这样明显的k-v定义,ThreadLocalMap.Entry继承了WeakReference<ThreadLocal< ? >>,并且,使用WeakReference<ThreadLocal< ? >>作为ThreadLocalMap的key。为什么要用WeakReference进行一层包装呢?为了防止内存泄漏,如果线程池中的Thread一直存在,那么ThreadLocal自然也会一直存在。

什么是弱引用?弱引用被垃圾回收器扫描到就会被回收,不会产生内存泄漏,这一点很清楚,但是问题是,这样就不怕需要使用的数据,被误回收吗,还需要了解实际的JVM引用机制,这块还没搞清楚,待补充

2022.03.26更新 发现这篇文章讲的ThreadLocal内存泄漏 非常棒,深入分析 ThreadLocal 内存泄漏问题

2022.04.16更新 ThreadLocal在MDC中的应用,MDC是非常实用的包,MDC是什么鬼?用法、源码一锅端