• 什么是ThreadLocal? 用来解决什么问题的?
  • 说说你对ThreadLocal的理解
  • ThreadLocal是如何实现线程隔离的?
  • 为什么ThreadLocal会造成内存泄露? 如何解决
  • 还有哪些使用ThreadLocal的应用场景?

一、ThreadLocal 介绍

1.1、ThreadLoacal 图示

image.png

1.2、ThreadLoacal 作用

实现线程安全的几种方式

  • 互斥同步: synchronized 和 ReentrantLock
  • 非阻塞同步: CAS, AtomicXXXX
  • 无同步方案: 栈封闭,本地存储(Thread Local)

ThreadLocal 是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用ThreadLocal来维护变量时, ThreadLocal 会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况。

1.3、ThreadLoacal 的缺点

  • ThreadLocal 容易造成内存泄露,用完需要手动 remove来避免
  • ThreadLocal 不能在父子线程之间传递,需要使用 InheritableThreadLocal 实现

二、ThreadLoacal 的使用

  1. //日志格式化,避免 SimpleDateFormat 多线程安全问题
  2. public class DateUtils {
  3. public static final ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
  4. @Override
  5. protected DateFormat initialValue() {
  6. return new SimpleDateFormat("yyyy-MM-dd");
  7. }
  8. };
  9. }

三、ThreadLoacal 源码解析

3.1、Thread、ThreadLoacal、ThreadLocalMap的关系

  • Thread 中持有 ThreadLocalMap 变量引用
  • ThreadLoacal 用于操作 ThreadLocalMap
  • ThreadLocalMap 是 ThreadLoacal 中的内部类

3.2、ThreadLoacal 初始化

  1. //构造函数
  2. public ThreadLocal() {
  3. }
  4. public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
  5. return new SuppliedThreadLocal<>(supplier);
  6. }
  7. //设置初始化值
  8. protected T initialValue() {
  9. return null;
  10. }

3.3、ThreadLoacal 读取过程

  • 获取值 ```java public T get() { //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程的 ThreadLocalMap 变量 //第一次获取,未初始化 ThreadLocalMap map = getMap(t); if (map != null) {
    1. ThreadLocalMap.Entry e = map.getEntry(this);
    2. if (e != null) {
    3. @SuppressWarnings("unchecked")
    4. T result = (T)e.value;
    5. return result;
    6. }
    } //进行初始化 return setInitialValue(); }
  1. - 第一次获取,进行初始化
  2. ```java
  3. private T setInitialValue() {
  4. //获取初始化的值
  5. T value = initialValue();
  6. //当前线程
  7. Thread t = Thread.currentThread();
  8. ThreadLocalMap map = getMap(t);
  9. if (map != null)
  10. //设置值
  11. map.set(this, value);
  12. else
  13. //创建ThreadLocalMap,并绑定到当前线程
  14. createMap(t, value);
  15. return value;
  16. }
  • 创建ThreadLocalMap,并绑定到当前线程
    1. // ThreadLocalMap 的key 是当前的 ThreadLoacal
    2. void createMap(Thread t, T firstValue) {
    3. //赋值给当前线程
    4. t.threadLocals = new ThreadLocalMap(this, firstValue);
    5. }

3.4、ThreadLoacal 写入过程

  • 写入值

    1. public void set(T value) {
    2. Thread t = Thread.currentThread();
    3. ThreadLocalMap map = getMap(t);
    4. if (map != null)
    5. //添加值到ThreadLocalMap 中
    6. map.set(this, value);
    7. else
    8. //创建 ThreadLocalMap
    9. createMap(t, value);
    10. }
  • 设置值到 ThreadLocalMap 中

    1. private void set(ThreadLocal<?> key, Object value) {
    2. // We don't use a fast path as with get() because it is at
    3. // least as common to use set() to create new entries as
    4. // it is to replace existing ones, in which case, a fast
    5. // path would fail more often than not.
    6. Entry[] tab = table;
    7. int len = tab.length;
    8. // 当前写入 哈希槽的下标索引
    9. //key.threadLocalHashCode 通过 AtomicInteger 递增
    10. int i = key.threadLocalHashCode & (len-1);
    11. for (Entry e = tab[i];
    12. e != null;
    13. //下一个元素 Entry,每次加一遍历
    14. e = tab[i = nextIndex(i, len)]) {
    15. //获取key
    16. ThreadLocal<?> k = e.get();
    17. //对于多个线程,同一个ThreadLocal时
    18. //会执行替换值
    19. if (k == key) {
    20. e.value = value;
    21. return;
    22. }
    23. if (k == null) {
    24. replaceStaleEntry(key, value, i);
    25. return;
    26. }
    27. }
    28. //创建Entry,添加到哈希槽中
    29. tab[i] = new Entry(key, value);
    30. int sz = ++size;
    31. if (!cleanSomeSlots(i, sz) && sz >= threshold)
    32. //扩容
    33. rehash();
    34. }

3.5、ThreadLoacal 删除过程

  1. public void remove() {
  2. ThreadLocalMap m = getMap(Thread.currentThread());
  3. if (m != null)
  4. m.remove(this);
  5. }
  1. private void remove(ThreadLocal<?> key) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. int i = key.threadLocalHashCode & (len-1);
  5. for (Entry e = tab[i];
  6. e != null;
  7. e = tab[i = nextIndex(i, len)]) {
  8. //遍历删除
  9. if (e.get() == key) {
  10. e.clear();
  11. expungeStaleEntry(i);
  12. return;
  13. }
  14. }
  15. }

3.6、ThreadLoacal 扩容

  1. private void rehash() {
  2. expungeStaleEntries();
  3. // Use lower threshold for doubling to avoid hysteresis
  4. //threshold 是 容量的 2/3
  5. if (size >= threshold - threshold / 4)
  6. resize();
  7. }
  1. private void resize() {
  2. Entry[] oldTab = table;
  3. int oldLen = oldTab.length;
  4. //容量翻倍
  5. int newLen = oldLen * 2;
  6. Entry[] newTab = new Entry[newLen];
  7. int count = 0;
  8. //拷贝到新的哈希槽
  9. for (int j = 0; j < oldLen; ++j) {
  10. Entry e = oldTab[j];
  11. if (e != null) {
  12. ThreadLocal<?> k = e.get();
  13. if (k == null) {
  14. e.value = null; // Help the GC
  15. } else {
  16. int h = k.threadLocalHashCode & (newLen - 1);
  17. while (newTab[h] != null)
  18. h = nextIndex(h, newLen);
  19. newTab[h] = e;
  20. count++;
  21. }
  22. }
  23. }
  24. //重写设置阀值
  25. setThreshold(newLen);
  26. size = count;
  27. table = newTab;
  28. }

四、其它 ThreadLoacal 实现

  • InheritableThreadLocal
    • 用于父子线程传递
    • 通过传递 ThreadLocalMap 实现
    • new Thread的时候init中,进行复制拷贝
  • TransmittableThreadLocal

总结

  • ThreadLocal 并不解决线程间共享数据的问题
  • ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
  • 每个线程持有一个 Map 并维护了 ThreadLocal 对象与具体实例的映射,该 Map 由于只被持有它的线程访问,故不存在线程安全以及锁的问题
  • ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为弱引用,避免了 ThreadLocal 对象无法被回收的问题
  • ThreadLocalMap 的 set 方法通过调用 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏
  • ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景

参考