ThreadLocal原理

1. ThreadLocal简介

  • ThreadLocal类提供了线程本地变量,每个线程都可以通过访问ThreadLocal对象获取到自己独立的变量副本,互不干扰。
  • 线程可以通过get()方法获取自己线程的本地副本,通过set()方法修改副本的值。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class ThreadLocalExample {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

    public static void main(String[] args) {
    Runnable task = () -> {
    int value = threadLocal.get();
    value += 1;
    threadLocal.set(value);
    System.out.println(Thread.currentThread().getName() + " Value: " + threadLocal.get());
    };

    Thread thread1 = new Thread(task, "Thread-1");
    Thread thread2 = new Thread(task, "Thread-2");

    thread1.start(); // 输出: Thread-1 Value: 1
    thread2.start(); // 输出: Thread-2 Value: 1
    }
    }

2. ThreadLocal原理

  • Thread类源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Thread implements Runnable {
    //......
    //与此线程有关的ThreadLocal值。由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;

    //与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    //......
    }
  • 从源码中可以看出,Thread类中有一个threadLocals和一个inheritableThreadLocals变量,类型均为ThreadLocal.ThreadLocalMap,默认值为null。只有当当前线程调用ThreadLocalset()get()方法时才会创建它们,实际上线程调用的是ThreadLocalMap类对应的get()set()方法。

  • ThreadLocalset()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public void set(T value) {
    //获取当前请求的线程
    Thread t = Thread.currentThread();
    //取出 Thread 类内部的 threadLocals 变量(哈希表结构)
    ThreadLocalMap map = getMap(t);
    if (map != null)
    // 将需要存储的值放入到这个哈希表中
    map.set(this, value);
    else
    createMap(t, value);
    }

    ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
    }
  • 通过代码可以看出:最终的变量是放在了当前线程的ThreadLocalMap中,并不是存在了ThreadLocal对象中。ThreadLocal类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)获取该线程的ThreadLocalMap对象。

  • ThreadLocalMapThreadLocal对象作为键,以Object对象为value进行存储,每个Thread中都有一个ThreadLocalMap,每访问一个ThreadLocal对象,就会在当前线程的ThreadLocalMap中创建一个键值对。

    1
    2
    3
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    ...
    }
  • ThreadLocal数据结构:

  • ThreadLocalMapThreadLocal的静态内部类。

2. ThreadLocal内存泄漏

  • ThreadLocal内存泄漏的根本原因在于其内部实现机制
  • ThreadLocalMapEntry定义如下:
    1
    2
    3
    4
    5
    6
    7
    8
    static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
    super(k);
    value = v;
    }
    }
  • 其中:
    • key(ThreadLocal 对象)是“弱引用”,也就是把ThreadLocal对象给丢了,JVM下轮GC就能把key给回收掉。
    • value是“强引用”,也就是只要Thread对象还在,value就不会被回收掉。
  • key被GC回收掉后,Map里会出现keynull,但value不为null的情况,理想情况下,下次访问ThreadLocalset/remove时,ThreadLocalMap会扫描并清理这些“僵尸key”,但是如果不在使用这个ThreadLocal,那么清理逻辑就永远不会触发,就会导致内存泄漏问题。
  • 内存泄漏的发生需要同时满足两个条件:
    1. ThreadLocal实例不再被强引用
    2. 线程持续活动,导致ThreadLocalMap长期存在