方法

set()

获取当前线程 根据线程从map中获取 value 如果换取不到则创建一个map key是当前线程 valua 是set的值
根据hashcode 找下标 然后拿到下标 从i 开始进行遍历进行线性探索 找如果当前下标已经有值了 则进行替换 如果没有值的话则添加

get()

remove()

withInitial()

原理

Dingtalk_20220420175428.jpg

线性探索

用来解决hash冲突的一种策略 是一种开放寻址的策略
通过hash 函数把key映射到hash 表中的一个位置来访问记录 从而加快查找的速度 存放记录的数据就是hash表(散列表)
当我们针对一个key通过hash函数计算产生一个位置 而这个位置在hash表中已经被另外一个键值对占比时 线性探索就可以解决这种情况
写入:查找hash表中冲突单元最近的空闲单元 把新的键值插入这个空闲单元
查找:从根据 hash函数计算得到的位置开始往后查找 直到找到与key对应的value或找到空的单元
就是个map key是指向当前 ThreadLocal实例的弱引用 value就是数据

ThreadLocalMap中的弱引用

  1. void createMap(Thread t, T firstValue) {
  2. t.threadLocals = new ThreadLocalMap(this, firstValue);
  3. }

set()

  1. public void set(T value) {
  2. //拿到当前线程
  3. Thread t = Thread.currentThread();
  4. //根据当前线程找当前线程的 ThreadLocalMap
  5. ThreadLocalMap map = getMap(t);
  6. //不等于空 直接set
  7. if (map != null) key ThreadLocal的弱引用的实例
  8. map.set(this, value);
  9. else
  10. //直接创建
  11. createMap(t, value);
  12. }
  1. //t是当前线程 t.threadLocals 表示当前线程的ThreadLocalMap
  2. // ThreadLocalMap 的key 是this 表示当前对象 当前对象是 ThreadLocal value 是输入的值
  3. void createMap(Thread t, T firstValue) {
  4. t.threadLocals = new ThreadLocalMap(this, firstValue);
  5. }
  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. //根据hash code找数组的下标
  9. int i = key.threadLocalHashCode & (len-1);
  10. //从i开始遍历 直到最后一个 线性探索
  11. for (Entry e = tab[i];
  12. e != null;
  13. e = tab[i = nextIndex(i, len)]) {
  14. ThreadLocal<?> k = e.get();
  15. //如果key相等则覆盖
  16. if (k == key) {
  17. e.value = value;
  18. return;
  19. }
  20. //如果key 为null 用新key value覆盖
  21. if (k == null) {
  22. //覆盖 并清理 key== null的数据
  23. replaceStaleEntry(key, value, i);
  24. return;
  25. }
  26. }
  27. tab[i] = new Entry(key, value);
  28. int sz = ++size;
  29. //超过阈值不需要扩容
  30. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  31. rehash();
  32. }

向前查找失效的entry向后查找可以被覆盖的entry
向前查找有失效的enrty 向后未找到可以覆盖的Entry
向前未查找到失效的entry 向后找到可以覆盖的Entry
向前未查找到失效的enrty 向后未查找到失效的entry

  1. private void replaceStaleEntry(ThreadLocal<?> key, Object value,
  2. int staleSlot) {
  3. Entry[] tab = table;
  4. int len = tab.length;
  5. Entry e;
  6. // Back up to check for prior stale entry in current run.
  7. // We clean out whole runs at a time to avoid continual
  8. // incremental rehashing due to garbage collector freeing
  9. // up refs in bunches (i.e., whenever the collector runs).
  10. //向前扫描 查找最前面无效的key
  11. int slotToExpunge = staleSlot;
  12. for (int i = prevIndex(staleSlot, len);
  13. (e = tab[i]) != null;
  14. i = prevIndex(i, len))
  15. if (e.get() == null)
  16. //循环遍历 定位最前面无效的key
  17. slotToExpunge = i;
  18. // Find either the key or trailing null slot of run, whichever
  19. // occurs first
  20. //从i开始向后查找 遍历数组的最后一个Entry
  21. for (int i = nextIndex(staleSlot, len);
  22. (e = tab[i]) != null;
  23. i = nextIndex(i, len)) {
  24. ThreadLocal<?> k = e.get();
  25. // If we find key, then we need to swap it
  26. // with the stale entry to maintain hash table order.
  27. // The newly stale slot, or any other stale slot
  28. // encountered above it, can then be sent to expungeStaleEntry
  29. // to remove or rehash all of the other entries in run.
  30. //找到匹配的key之后
  31. if (k == key) {
  32. e.value = value;
  33. //更新 value值
  34. tab[i] = tab[staleSlot];
  35. tab[staleSlot] = e;
  36. // Start expunge at preceding stale entry if it exists
  37. //如果最前面无效的key 和当前的key相同 责任将 i作为起点开始清理
  38. if (slotToExpunge == staleSlot)
  39. slotToExpunge = i;
  40. //清理
  41. cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
  42. return;
  43. }
  44. // If we didn't find stale entry on backward scan, the
  45. // first stale entry seen while scanning for key is the
  46. // first still present in the run.
  47. //如果当前的slot已经无效 并且在向前扫描的过程中没有无效的slot 则更新slotToExpunge当前位置
  48. if (k == null && slotToExpunge == staleSlot)
  49. slotToExpunge = i;
  50. }
  51. // If key not found, put new entry in stale slot
  52. tab[staleSlot].value = null;
  53. //如果key对应的value不存在 则直接放一个新的Entry
  54. tab[staleSlot] = new Entry(key, value);
  55. // If there are any other stale entries in run, expunge them
  56. //如果有任何无效的slot 则做一次清理
  57. if (slotToExpunge != staleSlot)
  58. cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
  59. }

get()

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. //根据当前线程拿到一个 ThreadLocalMap
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null) {
  6. //根据当前的ThreadLocal实例的软引用 拿value
  7. ThreadLocalMap.Entry e = map.getEntry(this);
  8. if (e != null) {
  9. @SuppressWarnings("unchecked")
  10. T result = (T)e.value;
  11. return result;
  12. }
  13. }
  14. return setInitialValue();
  15. }
  1. private T setInitialValue() {
  2. T value = initialValue();
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. return value;
  10. }

ThreadLocal内存泄漏

ThreadLocal每个线程用完后 都需要调用 remove()方法防止内存泄漏