ThreadLocal源码阅读

150 阅读5分钟

1. ThreadLocal的作用

我们看源码里ThreadLocal类的部分注释:

 * 这个类提供线程本地变量,允许每个线程独立拷贝自己的变量,可以通过get和set来获取和设置这些变量。
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 * </pre>
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 * 只要线程是存活的,并且ThreadLocal实例是可访问的,那么每个线程就会持有一份对其本地线程副本的隐式引用变量。
 * 当线程结束的时候,这个线程拷贝的线程本地实例将会被垃圾回收掉(除非存在其他引用指向这些数据)。
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {}

也就是在线程存活期间,允许每个线程拥有线程本地变量,保证各个线程各自的ThreadLocal变量各自独立,其他线程访问不到。

2. ThreadLocal里放的是什么?

我们来看几段源码: 1)Thread类里有2个变量,threadLocals和inheritableThreadLocals,两个变量都是ThreadLocalMap类型的变量,我们设置的当前线程本地变量其实就是放在这两个map里的。

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
```
```

2)ThreadLocal的set方法: 这个方法的作用是给Thread类的threadLocals变量赋值,设置的key是当前ThreadLocal本身,value是任何类型的值(比如们可以是一个Preson类型的值)

public void set(T value) {
    //1.获取当前线程
    Thread t = Thread.currentThread();
    //2.获取当前线程里的threadLocals变量,这个变量式ThreadLocalMap类型的变量,本质上是一个Map
    ThreadLocalMap map = getMap(t);
    //3.向map中设置key-value,key是当前对象(就是当前ThreadLocal本身),value可以是调用这个set()方法时传入的任何类型时值
    if (map != null)
        map.set(this, value);
    else
        //createMap方法是new了一个ThreadLocalMap并且赋值给线程t的threadLocals变量
        createMap(t, value);
}

这里面用到的两个方法:getMap(t)用来获取当前线程t的threadLocals变量

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

createMap()方法是创建了一个新的ThreadLocalMap,并赋值给当前线程t的threadLocals变量

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

总结:ThreadLocal存放线程本地变量,其实是将线程本地变量放在了ThreadLocalMap类型的一个Map里,key是当前ThreadLocal本身,value可以是任何类型的值。

3. 通过get()方法获取当前线程的本地变量

public T get() {
    //1.获取当前线程
    Thread t = Thread.currentThread();
    //2.获取当前线程的threadLocals
    ThreadLocalMap map = getMap(t);
    //3.如果当前线程的threadLocals为非空,从map中获取对应的value值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //3.如果当前线程的threadLocals为空,就设置并返回初始值,初始值为空
    return setInitialValue();
}
private T setInitialValue() {
    //这个方法返回一个null,所以在没有使用ste方法设置值的情况下,获取到的是一个null值
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

4. ThreadLocalMap

现在我们知道,ThreadLocal保存线程本地变量,实际是将数据保存在ThreadLocalMap里的,那么ThreadLocalMap的结构是什么呢? ThreadLocalMap实际上是一个Map,我们看ThreadLocalMap的源码,里面定义了一个继承自弱引用的Entry,那么我们调用ThreadLocal的set方法的时候,其实就是将数据封装成了一个这样的Entry。

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

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

4.1 ThreadLocalMap里的Entry为什么继承自弱引用 WeakReference

Java有4种引用:强、软、弱、虚。这里使用弱引用,是因为弱引用遇到GC就会被回收掉。我们分析一下: 假设我们创建了一个ThreadLocal tl = new ThreadLocal();那么这个th就是个强引用(普通的引用都是强引用),所以,此时有一个强引用tl指向我们新创建的ThreadLocal对象;然后ThreadLocal里还有一个Map叫做ThreadLocalMap,因为我们使用ThreadLocal其实就是将线程本地数据放入ThreadLocalMap里面,Map里其实是一个Entry,Entry里面的key就是ThreadLocal本身,value是我们放的数据,所以除了tl这个引用指向我们的ThreadLocal对象以外,还有一个Entry的key也指向我们的ThreadLocal对象。

ThreadLocal.jpg

所以,当我们的tl引用断开以后,其实还有一个key指向我们的ThreadLocal对象,由于这个key是个弱引用,所以当指向ThreadLocal对象的tl引用断开之后,那么我们的ThreadLocal对象就只有一个弱引用的key指向它,但是弱引用只要遇到GC就会被垃圾回收,所以这时不会产生内存泄漏。

4.2 ThreadLocal导致内存泄漏又是怎么产生的呢?

假设当指向我们的ThreadLocal对象的tl引用被回收了,key的指向也被回收了,如果这时候key指向了一个null值,但是threadLocals的Map是永远存在的,也就是说,如果key是指向null了,那么此时value这个对象还能被访问到吗?访问不到了,所以这个Map里的值会越来越多,还是会内存泄漏。

更多精彩内容请关注公众号:走在Java编程的路上

扫码_搜索联合传播样式-标准色版.png