是啥子?

ThreadLocal ,即thread 本身需要携带内容的处理; 与多线程无关,不涉及资源竞争;

怎么玩?

通常设置 ThreadLocal 为private static ,当一个线程结束时,ThreadLocal 的所有副本信息均可被回收;

代码

  1. /**
  2. * @desc threadLocal 使用
  3. * @author xxx
  4. * @date 2020/12/02 14:20
  5. */
  6. public class ThreadPoolDemo {
  7. private static final ThreadLocal<String> THREAD_NAME_LOCAL = new ThreadLocal<>();
  8. private static final ThreadLocal<TradeOrder> TRADE_THREAD_LOCAL = new ThreadLocal<>();
  9. public static void main(String[] args) {
  10. for (int i = 0; i < 2; i++) {
  11. int tradeId = i;
  12. new Thread(() -> {
  13. THREAD_NAME_LOCAL.set("name" + tradeId);
  14. TradeOrder tradeOrder = new TradeOrder(tradeId, tradeId % 2 == 0 ? "已支付" : "未支付");
  15. TRADE_THREAD_LOCAL.set(tradeOrder);
  16. System.out.println("threadName: " + THREAD_NAME_LOCAL.get());
  17. System.out.println("tradeOrder info:" + TRADE_THREAD_LOCAL.get());
  18. }, "thread-" + i).start();
  19. }
  20. }
  21. static class TradeOrder {
  22. long id;
  23. String status;
  24. public TradeOrder(int id, String status) {
  25. this.id = id;
  26. this.status = status;
  27. }
  28. @Override
  29. public String toString() {
  30. return "id=" + id + ", status=" + status;
  31. }
  32. }
  33. }

运行结果

  1. threadName: name0
  2. tradeOrder infoid=0, status=已支付
  3. threadName: name1
  4. tradeOrder infoid=1, status=未支付

为啥呢?

存储结构如下

threadLocal - 图1

原理分析

ThredLocalMap 在原生Thread 中的体现

  1. public class Thread implements Runnable {
  2. /*
  3. * InheritableThreadLocal values pertaining to this thread. This map is
  4. * maintained by the InheritableThreadLocal class.
  5. */
  6. // 由此看出ThreadLocalMap 为 java 原生Thread 的一个属性
  7. ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  8. }

我们从ThreadLocal set 方法入手分析;

  1. public void set(T value) {
  2. Thread t = Thread.currentThread(); // 获取当前线程(即调用线程)
  3. ThreadLocalMap map = getMap(t); // 获取当前线程绑定的ThreadLocalMap
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }
  9. void createMap(Thread t, T firstValue) { // 初始化ThreadLocalMap
  10. t.threadLocals = new ThreadLocalMap(this, firstValue);
  11. }
  12. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  13. table = new Entry[INITIAL_CAPACITY]; // 初始化大小为16的Entry 数组
  14. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 与运算获取位置
  15. table[i] = new Entry(firstKey, firstValue);
  16. size = 1;
  17. setThreshold(INITIAL_CAPACITY);
  18. }

从以上源码中我们可以直观看到set 过程,那么ThreadLocal 是如何解决hash 冲突的呢?

  1. public class ThreadLocal<T> {
  2. private final int threadLocalHashCode = nextHashCode(); // threadLocal hash定义
  3. private static AtomicInteger nextHashCode = new AtomicInteger();
  4. private static final int HASH_INCREMENT = 0x61c88647;
  5. // 获取下一个hash,在上一个hash 基础上+魔术(HASH_INCREMENT)
  6. private static int nextHashCode() {
  7. return nextHashCode.getAndAdd(HASH_INCREMENT);
  8. }
  9. }

因为有魔术的存在,可以保证结果均匀落在数组中;

常见问题

内存泄漏

内存泄漏:由于疏忽或错误造成程序未能释放已经不再使用的内存,从而造成了内存的浪费;

弱引用何时GC: 在内存不足的情况下,会被JVM 垃圾回收;

threadLocal内存泄漏,ThreadLocalMap 中的Entry ,弱引用ThreadLocal ; 即Map中的 Key 是弱引用;

  1. static class ThreadLocalMap {
  2. static class Entry extends WeakReference<ThreadLocal<?>> {
  3. /** The value associated with this ThreadLocal. */
  4. Object value;
  5. Entry(ThreadLocal<?> k, Object v) {
  6. super(k);
  7. value = v;
  8. }
  9. }
  10. }

使用弱引用目的在于在 没有强引用指向ThreadLocal 时,它可以被回收。避免ThreadLocal不能回收造成的内存泄露问题;

但可能出现ThreadLocal 被回收了,Value还存在(value 是强引用),由此造成的内存泄露;故需要在ThreadLocal 使用完毕后调用remove,移除Entry;

经典使用场景

  • 接口中获取客户端类型(避免此参数传递太深)

参见

Java进阶(七)正确理解Thread Local的原理与适用场景

20 | 技巧篇:Netty 的 FastThreadLocal 究竟比 ThreadLocal 快在哪儿?