Java ThreadLocal原理
为什么要有ThreadLocal
多线程利用锁来解决共享变量的问题,那如果每个线程都有自己的一份变量,就不涉及到线程安全的事情了。 当然这个变量不是方法里线程栈上的变量,是整个线程级别都可以访问的变量。
ThreadLocal出现就能解决这个问题,那它是怎么实现的呢?
看一段代码
每个线程有自己的threadlocal值
public class Test {
    /**
     * static final修饰 threadlocal变量
     * 每个线程里的ThreadLocalMap.Entry,key所有线程都是这一个变量,value各线程有各自的值
     */
    private static final ThreadLocal<String> threadlocalStr = ThreadLocal.withInitial(() -> "xx");
    public static void main(String[] args) throws InterruptedException {
        int threadNum = 4;
        // 4个线程
        ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
        for (int i = 0; i < threadNum; i++) {
            final int j = i;
            // 每个线程写自己的threadlocalStr
            executorService.submit(() -> {
                Test.threadlocalStr.set(Thread.currentThread().getName() + "-" + j);
            });
        }
        TimeUnit.SECONDS.sleep(2);
        for (int i = 0; i < threadNum; i++) {
            executorService.submit(() -> {
                // 每个线程读自己的threadlocalStr
                String s = Test.threadlocalStr.get();
                System.out.println(Thread.currentThread().getName() + " ==== " + s);
            });
        }
    }
}
// threadLocal.get()
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
ThreadLocal 原理
类引用关系
Thread -> ThreadLocal.ThreadLocalMap -> ThreadLocal.ThreadLocalMap.Entry[] -> ThreadLocal.ThreadLocalMap.Enrty -> key(threadLocal对象,是个弱引用)和value

类简要代码
class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
    public T get() {
        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();
    }
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    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;
            }
        }
    }
}
ThreadLocal 与弱引用
上述代码能看到 Entry构造函数中,弱引用即是ThreadLocal这个变量。
又根据上述类引用关系,知道是线程类Thread一步一步引用着 ThreadLocal,如果这个线程不结束(池化的线程通常不结束),那这个引用包括Entry里的value是不是造成内存泄露了呢?
弱引用
弱引用有什么特点?
弱引用对象被回收,是说这个弱引用对象只有,注意是只有,被弱引用引用着,才会在gc时候给回收。如果还在被强引用着,就不会回收。注意这个点。
来一段代码理解下弱引用:
public static void main(String[] args) {
  	// 强引用
    Person person = new Person("Peter");
  	// 弱引用
    WeakReference<Person> weakReference = new WeakReference<>(person);
    // 输出: Person(name=Peter)
    System.out.println(weakReference.get());
  	// 解除强引用
    person = null;
    // 输出: Person(name=Peter)
    // WeakReference的referent拿到的是person对象的地址,再让person指向null 也不影响referent的指向
    System.out.println(weakReference.get());
    // gc
    // 如果person = null打开,表示解除person的强引用,这时候person只有弱引用在用,需要回收掉person
    // 如果注释掉,那么person还有强引用在用,不会回收person
    System.gc();
    // 打开 person = null,因为person被gc 则输出null
  	// 注释 person = null,因为还有强引用不会gc 则输出: Person(name=Peter)
    System.out.println(weakReference.get());
}
@Data
@AllArgsConstructor
class Person {
    private String name;
}
ThreadLocal 为什么设计成弱引用呢?
上边理解了弱引用,这里也就能明白。
假设是强引用:一个对象内部持有一个threadLocal(这个threadLocal既被这个对象强引用,又被当前线程强引用),当这个对象变成垃圾以后,threadLocal就只被线程强引用了。 那如果线程不结束,这个threadLocal就一直不会被回收,造成内存泄露。
如果是弱引用,那么当宿主对象成垃圾时,threadLocal就只剩弱引用了,那么当GC时,它就可以被回收。
这还会有另一个问题,就是key被回收了,但是value还是被Entry强引用着,会无法回收。
那么value咋不设置弱引用呢?假设value是弱引用,当外部(非Thread类下)没有引用value时,那GC时value就没了,会导致ThreadLocal无法使用。
ThreadLocal 为什么能造成内存泄露
当作为key的threadLocal被回收了,但是value还是在被Entry对象强引用,线程不结束还是会内存泄露。
这也就是为什么建议我们使用完主动remove()的原因。
ThreadLocal 为什么建议用static修饰
ThreadLocalMap下包含的是Entry[]数组,当然一个线程支持多个threadLocal对象。那为什么建议用static修饰呢?
如果是static类级别的修饰,并且能达到这个ThreadLocal应用场景下的要求,那么创建更少的threadLocal自然可以节约资源。
还有一点,ThreadLocal内部会在set()、get()时帮我们清除没用的Entry 减少内存泄露的问题,如果是static修饰只有一个threadLocal 会降低我们在使用上的风险。
ThreadLocal 典型应用场景
- 事务上下文里,把数据库连接放到ThreadLocal里
- Hikari连接池中,把连接放到线程ThreadLocal里,方便下一次获取
- 上下文环境全局参数,避免方法间参数传递