ThreadLocal 源码分析

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

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 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3196 引用 • 8215 回帖

相关帖子

欢迎来到这里!

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

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