对ThreadLocal的理解

  • ThreadLocal能够提供线程的局部变量,让每个线程都可以通过操作set/get来对这个局部变量进行操作
  • 不会和其他线程的局部变量进行冲突,实现了线程的数据隔离

ThreadLocal的基本用法

  • 使用set(T value)方法将值存入ThreadLocalMap,如果map不存在,则进入set()内部的else分支创建一个ThreadLocalMap并将值value存入map,在创建的过程中会将这个map保存到当前线程对象的成员属性上

  • 然后调用get()方法获取值,get()内部会先从线程对象取出ThreadLocalMap,然后再取出其中的值,并返回

使用完后我们需要调用remove()方法清除ThreadLocalMap中存的内容,防止内存泄漏

注:Thread对象内部持有一个ThreadLocal.ThreadLocalMap threadLocals 对象,ThreadLocalMap看起来像一个map,可平常使用的过程中就相当于一个变量而已,只能存一个value,如果同一个线程调用两次set,那么只保存后面的set值!

简化的源码结构

  1. public class ThreadLocal<T> {
  2. private final int threadLocalHashCode = nextHashCode(); // 下一个hash值
  3. private static final int HASH_INCREMENT = 0x61c88647; // hash增量
  4. private static AtomicInteger nextHashCode = new AtomicInteger();// hash值的原子变量
  5. public ThreadLocal() {} // 构造方法
  6. /**
  7. * get方法、重点阅读
  8. */
  9. public T get() {
  10. Thread t = Thread.currentThread(); // 获取当前线程
  11. ThreadLocalMap map = getMap(t); // 获取当前线程绑定的ThreadLocalMap
  12. if (map != null) { // 如果map不为null
  13. ThreadLocalMap.Entry e = map.getEntry(this); // 获取当前线程绑定的数据的封装对象Entry(内部类)
  14. if (e != null) { // 如果当前数据不为null
  15. T result = (T) e.value; // 获取当前数据绑定的数据,进行了强转为泛型
  16. return result; // 返回数据
  17. }
  18. }
  19. return setInitialValue(); // map为null返回null
  20. }
  21. /**
  22. * set方法、重点阅读
  23. */
  24. public void set(T value) {
  25. Thread t = Thread.currentThread(); // 获取当前线程
  26. ThreadLocalMap map = getMap(t); // 获取当前线程绑定的 ThreadLocalMap
  27. if (map != null) { // map不为null
  28. map.set(this, value); // 给当前线程绑定的map中添加数据,key是当前线程、值是传入的value
  29. } else {
  30. createMap(t, value); // 当前线程没有map,创建一个map将传入的value存入map中并绑定到当前线程对象
  31. }
  32. }
  33. /**
  34. * remove方法、
  35. * 移除当前线程绑定的数据,
  36. * 每次使用完后就需要进行remove防止内存泄漏!!!
  37. */
  38. public void remove() {
  39. ThreadLocalMap m = getMap(Thread.currentThread()); // 获取当前线程绑定的map
  40. if (m != null) { // 存在map
  41. m.remove(this); // 清除当前线程绑定的数据
  42. }
  43. }
  44. public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {} // 返回一个新的ThreadLocal变量
  45. /**
  46. * 外部使用不到的方法
  47. */
  48. protected T initialValue() { return null; } // 初始值null
  49. private static int nextHashCode() { } // hash值加上0x61c88647并且将加完后的值返回
  50. private T setInitialValue() {} // 返回null并设置初始值null和set方法一样的逻辑,只是value为null
  51. ThreadLocalMap getMap(Thread t) { } // 得到线程t绑定的ThreadLocalMap
  52. void createMap(Thread t, T firstValue) {} // 创建ThreadLocalMap并添加key=t,value=firstValue到map中并绑定到当前线程
  53. boolean isPresent() {} // 是否存有数据,有则返回true、没有则返回false
  54. static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {} // 创建ThreadLocalMap,只在构造方法中使用
  55. T childValue(T parentValue) {} //
  56. // ThreadLocal类的扩展,获得规定的初始值Supplier(使用供给型的接口),实际上不需要了解,这个类我们外面使用不到
  57. static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
  58. private final Supplier<? extends T> supplier;
  59. SuppliedThreadLocal(Supplier<? extends T> supplier) { this.supplier = Objects.requireNonNull(supplier); }
  60. @Override
  61. protected T initialValue() { return supplier.get(); }
  62. }
  63. /**
  64. *
  65. */
  66. static class ThreadLocalMap {
  67. private static final int INITIAL_CAPACITY = 16; // 初始容量
  68. private Entry[] table; // 存储数据的结构
  69. private int size = 0; // 当前存了多少个数据
  70. private int threshold; // 扩容条件默认 10
  71. static class Entry extends WeakReference<ThreadLocal<?>> {
  72. Object value; // 真正存到ThreadLocal中的数据
  73. Entry(ThreadLocal<?> k, Object v) {} // Entry构造方法
  74. }
  75. // 构造方法
  76. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  77. table = new Entry[INITIAL_CAPACITY]; // 创建长度为16的Entry数组
  78. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 通过hash值&运算得到索引值
  79. table[i] = new Entry(firstKey, firstValue); // 将数据存入索引值所在的数组中
  80. size = 1; // 添加了第一个值,size变为1
  81. setThreshold(INITIAL_CAPACITY); // 设置扩容值 16*2/3 = 10
  82. }
  83. /**
  84. * 和上面的构造函数差不多,只不过传入的是一个map
  85. * 因此需要讲map中数据存入到线程所绑定的map中
  86. */
  87. private ThreadLocalMap(ThreadLocalMap parentMap) {}
  88. /**
  89. * 定义的方法用于在构造方法中使用,目的就是给成员变量赋值
  90. */
  91. private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {}
  92. private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {}
  93. private void resize() {} // 扩容
  94. private void rehash() {} // 再hash
  95. private Entry getEntry(ThreadLocal<?> key) {} // 根据key获取Entry
  96. private void setThreshold(int len) { } // 设置扩容条件值:threshold = len * 2 / 3;
  97. private static int nextIndex(int i, int len) {} // 上一个索引:return ((i + 1 < len) ? i + 1 : 0);
  98. private static int prevIndex(int i, int len) {} // 下一个索引:return ((i - 1 >= 0) ? i - 1 : len - 1);
  99. private void set(ThreadLocal<?> key, Object value) {} // 添加数据
  100. private void remove(ThreadLocal<?> key) {} // 移除key所对应的Entry数据
  101. private int expungeStaleEntry(int staleSlot) {} // 返回下一个null值得插槽索引
  102. private boolean cleanSomeSlots(int i, int n) {} // 如果索引i所对应的数据被移除返回true
  103. private void expungeStaleEntries() {} // 删除所有过时元素
  104. }
  105. }

使用场景

ThreadLocal的使用场景

  • springmvc的拦截器传参到控制层
  • 一个参数要传入多个方法
  • spring使用ThreadLocal来实现事务的控制,保证一次事务的所有操作需要在同一个数据库链接上
  • 。。。

关于ThreadLocal的内存泄露

面试题

1.为什么要把ThreadLocal作为key,而不是Thread作为Key?这样不是更清晰吗
答:你提出的做法实际上就是所有的线程都访问ThreadLocal的Map,而key是当前线程,但是这有一个问题,一个线程可以有多个私有变量,那如果key是当前线程的话,意味着还要坐点手脚来唯一表示set进去的value,并且还要一个问题,当并发足够大时,意味着所有的线程都去操作同一个map,map体积会膨胀,导致访问性能下降,而且这个map维护所有线程私有变量,以为这你不知道什么时候可以销毁