ThreadLocal 源码分析

本贴最后更新于 1700 天前,其中的信息可能已经事过境迁

ThreadLocal 是什么?

顾名思义,ThreadLocal 可以为每个线程独立存储不同的值(线程本地变量)。其意义在于高并发场景下变量被多个线程访问互不影响,有效避免线程安全问题和同步带来的性能开销。当然它也存在一定缺陷,由于每个线程都会创建 ThreadLocal 变量,就会带来一定的内存消耗;它的思想就是“以空间换时间”。

特殊情况:InhertiableThreadLocal 并不是只存储当前线程的值,它默认会集成父类中的值。

ThreadLocal 类方法定义

public class ThreadLocal<T> {
    public T get();
    private T setInitialValue();
    public void set(T value);
    public void remove();
}
  • get:用于获取当前线程私有的 ThreadLocal 变量;
  • setInitialValue:可以进行重写设置当前 ThreadLocal 对应的数据,用于第一次调用 get 时懒加载获取;
  • set:设置当前 Thread 的 ThreadLocal 值;
  • remove:删除 ThreadLocal 中的数据;

get 方法具体实现

   /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     * @return the current thread's value of this thread-local
     */
    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取Thread类中定义的ThreadLocal.ThreadLocalMap变量
        ThreadLocalMap map = getMap(t);
        //判断map是否为null
        if (map != null) {
            //获取当前ThreadLocal对应的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                //获取set的值
                @SuppressWarnings("unchecked")
                T result = (T) e.value;
                return result;
            }
        }
        //当map为null或未设置值时调用
        return setInitialValue();
    }

其中 ThreadLocalMap 是 ThreadLocal 中的静态内部类,内部维护了一个 Entry 数组的 table,可以看作是一个 kv 的 map,只不过 key 是当前 ThreadLocal 生成的 Hash 值;

static class ThreadLocalMap {

	private Entry[] table;

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

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

setInitialValue 具体实现

当 get 方法中 map 为 null 或未设置值时会调用 setInitialValue 方法,接下来看看它的实现:

   /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     * @return the initial value
     */
    private T setInitialValue() {
        //value等于重写initialValue后返回的值,否则默认返回null
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前Thread类中定义的ThreadLocal.ThreadLocalMap变量
        ThreadLocalMap map = getMap(t);
        //map为null直接添加到table中,否则新建map
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

值得注意的是其中 initialValue 默认是返回 null 的,当然你也可以选择重写来懒加载数据,当调用 get 方法的时候才获取,就像这样:

ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
	int i = 1;

	@Override
	protected Integer initialValue() {
	    return i;
	}
};

其中当 map 为 null 是会调用 createMap 方法,将创建的 ThreadLocalMap 赋值到当前 Thread 的 threadLocals 变量,完成关联!

/**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     * @param t
     *         the current thread
     * @param firstValue
     *         value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
public
class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

set 方法具体实现

和上面 setInitialValue 中实现几乎一样,这里就不赘述了...

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     * @param value
     *         the value to be stored in the current thread's copy of
     *         this thread-local.
     */
    public void set(T value) {
        //获取当前Thread
        Thread t = Thread.currentThread();
        //获取当前Thread类中定义的ThreadLocal.ThreadLocalMap变量
        ThreadLocalMap map = getMap(t);
        //map为null直接添加到table中,否则新建map
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

remove 方法具体实现

获取当前线程中的 ThreadLocal.ThreadLocalMap,清除 table 数组中以当前 ThreadLocal Hash 为 key 的数据。

   /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     * @since 1.5
     */
    public void remove() {
        //获取当前Thread类中定义的ThreadLocal.ThreadLocalMap变量
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            //删除以当前ThreadLocal为key设置的数据
            m.remove(this);
    }

关于内存泄露的问题

抛出问题
  • 首先 ThreadLocal 实例被线程的 ThreadLocalMap 实例持有,也可以看成被线程持有。
  • 如果应用使用了线程池,那么之前的线程实例处理完之后出于复用的目的依然存活
    所以,ThreadLocal 设定的值被持有,导致内存泄露。
结论

首先 Entry 是继承 WeakReference<ThreadLocal<?>> 的,也就是弱引用,ThreadLocalMap 的 key 使用的是 ThreadLocal 的弱引用,所以并不会导致内存泄露,关于 java 中的四种引用我会在后面的文章中记录。

最后

第一次写源码分析,有不好的地方欢迎指正。

参考资料

https://github.com/decaywood/decaywood.github.io/blob/master/_posts/2016/3/2016-03-15-ThreadLocal-intro.markdown

理解Java中的ThreadLocal - 技术小黑屋

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...