ThreadLocal

52 阅读5分钟
  • ThreadLocal 是用于保存当前线程内的共享变量,其他线程访问不到,其核心数据结构是 ThreadLocalMap ,每个 Thread 会持有一个自己的 ThreadLocalMap

1、核心方法

(1)set

public void set(T value) {
  // 获取当前线程
  Thread t = Thread.currentThread();
  // 获取当前线程的 ThreadLocalMap
  ThreadLocalMap map = getMap(t);
  if (map != null)
    // 如果 ThreadLocalMap 不为空,将当前 ThreadLocal 对象作为 key,和 value 放入 ThreadLocalMap
    map.set(this, value);
  else
    // 如果 ThreadLocalMap 为空,创建一个 ThreadLocalMap ,放入数据,并赋值给当前 Thread
    createMap(t, value);
}

(2)get

public T get() {
  // 获取当前线程
  Thread t = Thread.currentThread();
  // 获取当前线程的 ThreadLocalMap
  ThreadLocalMap map = getMap(t);
  if (map != null) {
    // 从 ThreadLocalMap 中获取当前 ThreadLocal 的 Entry
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
      // 如果 Entry 存在,返回具体的值
      @SuppressWarnings("unchecked")
      T result = (T)e.value;
      return result;
    }
  }
  // 如果 ThreadLocalMap 是空的,初始化一个 ThreadLocalMap ,value 为 null 放进去,并返回
  // 其实具体的逻辑等价于 set(null) 并返回 null
  return setInitialValue();
}

(3)remove

public void remove() {
  // 获取当前线程的 ThreadLocalMap
  ThreadLocalMap m = getMap(Thread.currentThread());
  if (m != null)
    // 如果 ThreadLocalMap 不为空,移除当前 ThreadLocal
    m.remove(this);
}

2、ThreadLocalMap

  • ThreadLocalMap 其结构类似于 HashMap,维护了一个 Entry 数组,Entry 对象包括一个 key 弱引用的 ThreadLocal 对象和 value 具体的值

(1)构造方法

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  // 创建一个默认长度(16)的数据
  table = new Entry[INITIAL_CAPACITY];
  // 计算当前 key 的下标
  int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  // 创建一个 Entry 对象放入对应下标处
  table[i] = new Entry(firstKey, firstValue);
  // 设置长度为1
  size = 1;
  // 设置扩容阈值(数组长度 * 2 / 3)
  setThreshold(INITIAL_CAPACITY);
}

(2)set

private void set(ThreadLocal<?> key, Object value) {
​
  // We don't use a fast path as with get() because it is at
  // least as common to use set() to create new entries as
  // it is to replace existing ones, in which case, a fast
  // path would fail more often than not.
​
  // 获取 Entry 数组
  Entry[] tab = table;
  // 获取 Entry 数组的长度
  int len = tab.length;
  // 计算当前 key 的下标
  int i = key.threadLocalHashCode & (len-1);
​
  // 循环处理下标元素不为空的情况(可能是 hash 冲突或者之前被添加过)
  for (Entry e = tab[i];
       e != null;
       e = tab[i = nextIndex(i, len)]) {
    // 获取 key 的 ThreadLocal
    ThreadLocal<?> k = e.get();
​
    if (k == key) {
      // 如果两个 key 相同,更新 value 后返回
      e.value = value;
      return;
    }
​
    if (k == null) {
      // 如果 key 是空的,清空 Entry 再赋值后返回
      replaceStaleEntry(key, value, i);
      return;
    }
  }
​
  // 下标处元素为空直接赋值
  tab[i] = new Entry(key, value);
  // 处理当前元素个数
  int sz = ++size;
  // 清空 key = null 的数据,并且判断是否需要扩容
  if (!cleanSomeSlots(i, sz) && sz >= threshold)
    // 扩容
    rehash();
}

(3)getEntry

private Entry getEntry(ThreadLocal<?> key) {
  // 计算当前 key 的下标
  int i = key.threadLocalHashCode & (table.length - 1);
  // 获取下标处 Entry
  Entry e = table[i];
  if (e != null && e.get() == key)
    // 如果 Entry 不为空并且 key 相同,返回 Entry
    return e;
  else
    // 找到 key 相同的 Entry 返回,没有直接返回 null ,并处理 key = null 的情况 
    return getEntryAfterMiss(key, i, e);
}

(4)remove

private void remove(ThreadLocal<?> key) {
  // 获取 Entry 数组
  Entry[] tab = table;
  // 获取 Entry 数组长度
  int len = tab.length;
  // 计算 key 的下标
  int i = key.threadLocalHashCode & (len-1);
  for (Entry e = tab[i];
       e != null;
       e = tab[i = nextIndex(i, len)]) {
    // 找到 key 相同的 Entry
    if (e.get() == key) {
      // 清空 Entry 的 key 设置为 null
      e.clear();
      // 清理 key 为 null 的 Entry
      expungeStaleEntry(i);
      return;
    }
  }
}

3、InheritableThreadLocal

  • InheritableThreadLocal 主要是为了解决子线程想获取父线程 ThreadLocal 的情况,其核心原理是在子线程创建的时候对父线程的 InheritableThreadLocal 做一个浅拷贝,只是重新创建了 ThreadLocalMap 和 Entry 数组,Entry 数组中的 key 和 value 并没有重新创建对象

(1)继承 InheritableThreadLocal

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
  // 根据父线程的 inheritableThreadLocals 创建一个新的 ThreadLocalMap 赋值给自己的 inheritableThreadLocals
  this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
​
// 最终会调用 ThreadLocalMap 的构造方法创建
private ThreadLocalMap(ThreadLocalMap parentMap) {
  Entry[] parentTable = parentMap.table;
  int len = parentTable.length;
  setThreshold(len);
  table = new Entry[len];
​
  for (int j = 0; j < len; j++) {
    Entry e = parentTable[j];
    if (e != null) {
      @SuppressWarnings("unchecked")
      ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
      if (key != null) {
        Object value = key.childValue(e.value);
        Entry c = new Entry(key, value);
        int h = key.threadLocalHashCode & (len - 1);
        while (table[h] != null)
          h = nextIndex(h, len);
        table[h] = c;
        size++;
      }
    }
  }
}

(2)childValue

// 直接返回传过来的 value 没做任何处理
protected T childValue(T parentValue) {
  return parentValue;
}

(3)getMap

// 返回该线程的 inheritableThreadLocals
ThreadLocalMap getMap(Thread t) {
  return t.inheritableThreadLocals;
}

(4)createMap

// 创建 ThreadLocalMap 并赋值给当前线程的 inheritableThreadLocals
void createMap(Thread t, T firstValue) {
  t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

4、内存泄漏问题

ThreadLocal引用.png

  • 引用链

    • ThreadLocal 引用 -> ThreadLocal 对象
    • Thread 引用 -> Thread 对象 -> ThreadLocalMap 对象 -> Entry 数组 -> Entry -> Entry 弱引用 ThreadLocal 对象、强引用 value 对象
  • 主动 remove 的情况

Thread 引用存在Thread 引用不存在
ThreadLocal 引用存在ThreadLocal 对象不会回收
value 对象会在下一次 gc 时回收
ThreadLocal 对象不会回收
value 对象会在下一次 gc 时回收
ThreadLocal 引用不存在ThreadLocal 对象会在下一次 gc 时回收
value 对象会在下一次 gc 时回收
ThreadLocal 对象会在下一次 gc 时回收
value 对象会在下一次 gc 时回收
  • 不主动 remove 的情况
Thread 引用存在Thread 引用不存在
ThreadLocal 引用存在ThreadLocal 对象不会回收
value 对象不会回收
ThreadLocal 对象不会回收
value 对象会在下一次 gc 时回收
ThreadLocal 引用不存在ThreadLocal 对象会在下一次 gc 时回收
value 对象会在下一次操作 ThreadLocalMap 时断开引用,断开引用后下一次 gc 时回收
ThreadLocal 对象会在下一次 gc 时回收
value 对象会在下一次 gc 时回收
  • 在日常开发中,请求都是由线程池去处理,所以 Thread 引用绝大多数情况是一直都存在的,就算 ThreadLocal 引用不存在,如果 Thread 没有再操作过 ThreadLocalMap,value 对象是不会被回收的,况且还有通过类的静态变量引用 ThreadLocal 对象导致 ThreadLocal 引用一直存在的情况