定义

ThreadLocal的主要作用就是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程来说是相对隔离的,在多线程的情况下,防止自己的变量被其他线程篡改。

隔离有什么用,会用在什么场景?

Spring的事务隔离级别的实现

Spring采用ThreadLocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理Connection, 通过传播级别,巧妙的管理多个事务配置之间的切换、挂起和恢复。

Spring的事务主要就是通过ThreadLocal和AOP实现的,每个线程自己的链接都是通过ThreadLocal来保存的。

上下文中cookie、session等上下文用户信息

底层实现原理

使用

线程进来之后初始化一个可以泛型的ThreadLocal对象,之后这个线程只要在remove之前去get,都能拿到之前set的值,注意这里说是remove之前。

  1. package com.interview.demo;
  2. /**
  3. * @Author leijs
  4. * @date 2022/4/2
  5. */
  6. public class ThreadLocalDemo {
  7. public static void main(String[] args) {
  8. ThreadLocal<String> threadLocal = new ThreadLocal<>();
  9. threadLocal.set("123");
  10. System.out.println(threadLocal.get());
  11. }
  12. }

set 方法

image.png
image.png
主要关注下ThreadLocalMap.
image.png
这就是ThreadLocal数据隔离的真相,每个线程Thread都维护了自己的threadLocals变量,所以在每个线程创建ThreadLocal的时候,实际上数据是存在自己线程Thread的threadLocals变量里面,别人没法拿到。从而实现了隔离。

ThreadLocalMap底层结构是什么样子的?

  1. static class ThreadLocalMap {
  2. /**
  3. * The entries in this hash map extend WeakReference, using
  4. * its main ref field as the key (which is always a
  5. * ThreadLocal object). Note that null keys (i.e. entry.get()
  6. * == null) mean that the key is no longer referenced, so the
  7. * entry can be expunged from table. Such entries are referred to
  8. * as "stale entries" in the code that follows.
  9. */
  10. static class Entry extends WeakReference<ThreadLocal<?>> {
  11. /** The value associated with this ThreadLocal. */
  12. Object value;
  13. Entry(ThreadLocal<?> k, Object v) {
  14. super(k);
  15. value = v;
  16. }
  17. }
  18. /**
  19. * The initial capacity -- MUST be a power of two.
  20. */
  21. private static final int INITIAL_CAPACITY = 16;
  22. /**
  23. * The table, resized as necessary.
  24. * table.length MUST always be a power of two.
  25. */
  26. private Entry[] table;
  27. /**
  28. * The number of entries in the table.
  29. */
  30. private int size = 0;
  31. /**
  32. * The next size value at which to resize.
  33. */
  34. private int threshold; // Default to 0
  35. /**
  36. * Set the resize threshold to maintain at worst a 2/3 load factor.
  37. */
  38. private void setThreshold(int len) {
  39. threshold = len * 2 / 3;
  40. }
  41. /**
  42. * Increment i modulo len.
  43. */
  44. private static int nextIndex(int i, int len) {
  45. return ((i + 1 < len) ? i + 1 : 0);
  46. }
  47. /**
  48. * Decrement i modulo len.
  49. */
  50. private static int prevIndex(int i, int len) {
  51. return ((i - 1 >= 0) ? i - 1 : len - 1);
  52. }
  53. /**
  54. * Construct a new map initially containing (firstKey, firstValue).
  55. * ThreadLocalMaps are constructed lazily, so we only create
  56. * one when we have at least one entry to put in it.
  57. */
  58. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  59. table = new Entry[INITIAL_CAPACITY];
  60. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  61. table[i] = new Entry(firstKey, firstValue);
  62. size = 1;
  63. setThreshold(INITIAL_CAPACITY);
  64. }
  65. /**
  66. * Construct a new map including all Inheritable ThreadLocals
  67. * from given parent map. Called only by createInheritedMap.
  68. *
  69. * @param parentMap the map associated with parent thread.
  70. */
  71. private ThreadLocalMap(ThreadLocalMap parentMap) {
  72. Entry[] parentTable = parentMap.table;
  73. int len = parentTable.length;
  74. setThreshold(len);
  75. table = new Entry[len];
  76. for (int j = 0; j < len; j++) {
  77. Entry e = parentTable[j];
  78. if (e != null) {
  79. @SuppressWarnings("unchecked")
  80. ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
  81. if (key != null) {
  82. Object value = key.childValue(e.value);
  83. Entry c = new Entry(key, value);
  84. int h = key.threadLocalHashCode & (len - 1);
  85. while (table[h] != null)
  86. h = nextIndex(h, len);
  87. table[h] = c;
  88. size++;
  89. }
  90. }
  91. }
  92. }

数据结构很像HashMap,但是并没有实现Map接口,而且它的Entry继承了WeakReference(弱引用),也没有看到hashMap的next,所以也不存在链表。

大概结构:Entry的数组 -> 每个Entry = ThreadLocal + value
image.png

为什么需要数组?没有链表怎么解决hash冲突?

用数组是因为一个线程可以有多个ThreadLocal来存放不同类型的对对象,但是他们都将放到你当前线程的ThreadLocalMap里面去。所以用数组。
解决hash冲突:
ThreadLocalMap在存储的时候会给每一个ThreadLocal对象一个threadLocalHashCode,在插入过程中,

  • 根据hash值,定位到table的位置i.如果该位置是空的,就初始化一个Entry对象放在该位置
  • 如果不为空,这个entry对象的key就是即将设置的key,那么就刷新entry中的value.
  • 如果位置i不为空且key不等于entry,那就找下一个空位置,直到为空为止。

image.png
这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置的entry对象中的key和get的key是否一致,不一致就判断下一个位置,set和get如果冲突严重的话,效率还是很低的。

对象存放在哪里?

栈内存归属于单个线程,每个线程都有一个栈内存。

ThreadLocal的实例以及其值存放在栈上吗?

不是。ThreadLocal实例实际上也是被其创建的类持有,ThreadLocal的值其实也是被线程实例持有,都位于堆上。只是通过了一些技巧将可见性修改成了线程可见。

共享线程的ThreadLocal数据怎么办?

InheritableThreadLocal可以实现多个线程访问ThreadLocal的值。主线程中创建一个InheritableThreadLocal的实例,子线程可以拿到。

  1. package com.interview.demo;
  2. /**
  3. * @Author leijs
  4. * @date 2022/4/2
  5. */
  6. public class ThreadLocalDemo {
  7. public static void main(String[] args) {
  8. ThreadLocal<String> threadLocal = new ThreadLocal<>();
  9. threadLocal.set("123");
  10. System.out.println(threadLocal.get());
  11. ThreadLocal<String> threadLocal1 = new InheritableThreadLocal<>();
  12. threadLocal1.set("java");
  13. new Thread(() -> {
  14. System.out.println("hello " + threadLocal1.get());
  15. }).start();
  16. }
  17. }

怎么传递的?

image.png
Thread的init方法,会去判断父类的inheritableThreadLocals存在,然后把父线程的inheritableThreadLocals设置给当前子线程的inheritableThreadLocals。

ThreadLocal内存泄漏问题

ThreadLocal在保存的时候把自己当作key存放到ThreadLocalMap中,正常情况下key和value都应该是强引用,但是key被设计成弱引用。
ThreadLocal再没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直在运行,那么这个Entry对象的value就可能一直得不到回收,发生内存泄漏。按照道理一个线程使用完,ThreadLocalMap应该是要被清空的,但是现在线程被复用了。

怎么解决?

代码最后remove就好了。

为什么ThreadLocalMap中的key要设计成弱引用?

不设计成弱引用,entry中的value一样存在内存泄漏问题。