学习目标

读懂 ThreadLocal 源码,理解其内部实现原理。根据 ThreadLocal 特点,总结出使用场景。

过程记录

第一次阅读 ThreadLocal 源码,在 IDE 中打开ThreadLocal.java 源文件,算上注释这个类下一共才 800 行不到的代码。之前没有阅读过相关文章,我们可以一点一点看起。
先粗略了看了一下类的内部,包含两个静态内部类 SuppliedThreadLocalThreadLocalMap、三个成员变量和若干个方法(没有数)。

ThreadLocal 的成员

  1. // 当前的 HashCode 值
  2. private final int threadLocalHashCode = nextHashCode();
  3. // AtomicInteger 提供了原子性的操作
  4. private static AtomicInteger nextHashCode = new AutomicInteger();
  5. // 魔法值
  6. private static final int HASH_INCREMENT = 0x61c88647;
  7. // 每当创建 ThreadLocal 实例,这个值都会累加魔数
  8. private static int nextHashCode() {
  9. return nextHashCode.getAndAdd(HASH_INCREMENT);
  10. }

为什么是 0x61c88647 ? 为了让哈希码能均匀的分布在2的N次方的数组里,0x61c88647 可以有效的降低碰撞概率;

ThreadLocal 的 get 与 set

get()
get() 方法,首先通过 Thread.currentThread() 获取当前线程
getMap 返回了当前 Thread 中的 threadLocals成员变量

  1. public T get() {
  2. // 获取当前线程
  3. Thread t = Thread.currentThread();
  4. // 获取 ThreadLocalMap
  5. ThreadLocalMap map = getMap(t);
  6. if (map != null) {
  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. }

如果 返回的 map 为空,则会调用 setInitialValue()
setInitialValue()方法第一行会调用 initialValue()
然后再在当前线程中获取一次 ThreadLocalMap 如果不此时不为空,就把 initialValue 返回的值通过 ThreadLocalMapset() 方法设置进去。如果 ThreadLocalMap 为空,则调用 createMap 方法。

  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. }

createMap 方法内调用了 ThreadLocalMap(ThreadLocal<?> firstKey``, ``Object firstValue)构造方法

  1. void createMap(Thread t, T firstValue) {
  2. t.threadLocals = new ThreadLocalMap(this, firstValue);
  3. }
  1. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  2. // 初始化 Entry[]
  3. // Default INITIAL_CAPACITY = 16 必须是2的倍数
  4. table = new Entry[INITIAL_CAPACITY];
  5. // 开放地址法 - 位运算,结果与取模相同,计算出需要存放的位置
  6. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  7. // 将数据复制到对应位置
  8. table[i] = new Entry(firstKey, firstValue);
  9. // 设置 ENTRY 中的元素个数
  10. size = 1;
  11. // 设置扩容条件 超过2/3 即扩容
  12. setThreshold(INITIAL_CAPACITY);
  13. }

Entry 通过弱引用的方式继承了 ThreadLocal,实现了 Key - Value 的形式

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

ThreadLocalMap

  1. private void set(ThreadLocal<?> key, Object value) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. // 获得存放的位置
  5. int i = key.threadLocalHashCode & (len-1);
  6. for (Entry e = tab[i];
  7. e != null;
  8. e = tab[i = nextIndex(i, len)]) {
  9. // 顺序遍历 tab 上的位置数据
  10. ThreadLocal<?> k = e.get();
  11. if (k == key) {
  12. // 设置值
  13. e.value = value;
  14. return;
  15. }
  16. // 如果 k 为 null时,说明弱引用已经失效了
  17. if (k == null) {
  18. // 替换掉过时的 value
  19. replaceStaleEntry(key, value, i);
  20. return;
  21. }
  22. }
  23. tab[i] = new Entry(key, value);
  24. int sz = ++size;
  25. // 调用 cleanSomeSlots 清楚无效的 entry
  26. // 如果满足条件测触发 rehash()
  27. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  28. rehash();
  29. }
  1. private void replaceStaleEntry(ThreadLocal<?> key, Object value,
  2. int staleSlot) {
  3. Entry[] tab = table;
  4. int len = tab.length;
  5. Entry e;
  6. // 这里采用从当前的 staleSlot 位置向前遍历
  7. // 是为了把前面所有的已经被回收的也一起释放空间出来
  8. int slotToExpunge = staleSlot;
  9. for (int i = prevIndex(staleSlot, len);
  10. (e = tab[i]) != null;
  11. i = prevIndex(i, len))
  12. if (e.get() == null)
  13. // 记录当前左手边第一个空的 entry 到staleSlot 之间key过期最小的index
  14. slotToExpunge = i;
  15. // 获取 staleSlot 右边第一个空的 entry
  16. for (int i = nextIndex(staleSlot, len);
  17. (e = tab[i]) != null;
  18. i = nextIndex(i, len)) {
  19. ThreadLocal<?> k = e.get();
  20. // 说明之前已经存在相同的key, 要替换旧值并和前面的
  21. // 过期对象进行位置交换
  22. if (k == key) {
  23. e.value = value;
  24. tab[i] = tab[staleSlot];
  25. tab[staleSlot] = e;
  26. // 如果之前向前找, 没有找到过期的
  27. // 前面已经把向后找的放到了[i]上
  28. // 所以清理位置 i
  29. if (slotToExpunge == staleSlot)
  30. slotToExpunge = i;
  31. cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
  32. // 清理了之后就会退出
  33. return;
  34. }
  35. // 如果向前找没有找到过期对象
  36. // 那么就向后找, 如果向后找也没有找到
  37. // 那么整个数据组都没有过期的 key , 那就把该 key 设置到 staleSlot 位置上
  38. // staleSlot 位置总是存放的有效值
  39. if (k == null && slotToExpunge == staleSlot)
  40. slotToExpunge = i;
  41. }
  42. // 如果 key 不存在, 新建一个新的 entry 放进去
  43. tab[staleSlot].value = null;
  44. tab[staleSlot] = new Entry(key, value);
  45. // 清理其他过期对象
  46. if (slotToExpunge != staleSlot)
  47. // 清理
  48. cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
  49. }
  1. // 删除过期 entry, 帮助垃圾回收
  2. private int expungeStaleEntry(int staleSlot) {
  3. Entry[] tab = table;
  4. int len = tab.length;
  5. // 当前statleSlot 传入的是无效 entry 的 index
  6. tab[staleSlot].value = null;
  7. tab[staleSlot] = null;
  8. size--;
  9. Entry e;
  10. int i;
  11. // 往后寻找
  12. for (i = nextIndex(staleSlot, len);
  13. (e = tab[i]) != null;
  14. i = nextIndex(i, len)) {
  15. ThreadLocal<?> k = e.get();
  16. if (k == null) {
  17. e.value = null;
  18. tab[i] = null;
  19. size--;
  20. } else {
  21. // 因为是采用的是开放地址法, 所以删除的元素是多个冲突元素转给你的一个
  22. // 需要对后面元素往前移动, 如果不移动的话,后面的元素就永远访问不到了
  23. int h = k.threadLocalHashCode & (len - 1);
  24. // 不相等,说明是有冲突的
  25. if (h != i) {
  26. tab[i] = null;
  27. while (tab[h] != null)
  28. h = nextIndex(h, len);
  29. tab[h] = e;
  30. }
  31. }
  32. }
  33. return i;
  34. }
  1. private boolean cleanSomeSlots(int i, int n) {
  2. boolean removed = false;
  3. Entry[] tab = table;
  4. int len = tab.length;
  5. do {
  6. i = nextIndex(i, len);
  7. Entry e = tab[i];
  8. if (e != null && e.get() == null) {
  9. n = len;
  10. removed = true;
  11. i = expungeStaleEntry(i);
  12. }
  13. } while ( (n >>>= 1) != 0);
  14. return removed;
  15. }

rehash()

  1. private void rehash() {
  2. // 清除过期 entry
  3. expungeStaleEntries();
  4. // Use lower threshold for doubling to avoid hysteresis
  5. if (size >= threshold - threshold / 4)
  6. resize();
  7. }
  8. // 再扩充一倍 table
  9. private void resize() {
  10. Entry[] oldTab = table;
  11. int oldLen = oldTab.length;
  12. int newLen = oldLen * 2;
  13. Entry[] newTab = new Entry[newLen];
  14. int count = 0;
  15. // 把旧数据重新 hash 一遍放到新数组里
  16. for (int j = 0; j < oldLen; ++j) {
  17. Entry e = oldTab[j];
  18. if (e != null) {
  19. ThreadLocal<?> k = e.get();
  20. if (k == null) {
  21. // 如果 key 已经失效了,则把 value 也置为空
  22. e.value = null; // Help the GC
  23. } else {
  24. // 有效的, 则根据新数组长度计算一遍位置
  25. int h = k.threadLocalHashCode & (newLen - 1);
  26. // 冲突则找下一个位置
  27. while (newTab[h] != null)
  28. h = nextIndex(h, newLen);
  29. newTab[h] = e;
  30. count++;
  31. }
  32. }
  33. }
  34. // 重新设置触发扩容的长度
  35. setThreshold(newLen);
  36. // 重新设置元素个数
  37. size = count;
  38. // 重新设置数组
  39. table = newTab;
  40. }

总结

  1. 使用 ThreadLocal 记录线程本地变量时, 会将ThreadLocalMap 绑定到本地线程上
  2. Hash 使用的是开放地址法, 每次增加 0x61c88647 是为了减少碰撞次数,使得能够更均匀的分布在2的N次方数组里
  3. ThreadLocalMap 并不是一个 Map 结构,内部的 Entry 而是使用了弱引用 LocalThread 为 Key,内部对象 Object value 作为 value 的一个 Key - Value 实现。
  4. 默认的桶的数量为 16 ,初始化应该为 2 的倍数, 满足了resize() 条件后, 则会扩充2倍
  5. 通过复写 initialValue() 实现默认值的返回
  6. 调用 get() 和 set() 方法时,都可能会触发调用 expungeStaleEntry() 对过期 key 的清除,避免内存泄露
  7. 如果set值时,index 冲突了,则会向后找位置, 在向后找位置的过程中如果遇到过期的Key 值则会触发 replaceStaleEntry 方法清除过期 entry ,replaceStaleEntry 接收三个参数,Key -> 要设置的 ThreadLocal ,Value -> Object ,以及当前过期的位置 i,方法先在 index(i) 查找左边过期的 entry 的 index 最小值,然后再获取右边过期的 key, 如果左边有过期的key,查看是否与有要设置的 key 是相同的,如果有的话先把新的Value 值设置到有效的 entry 上,并把当前 statleSlot 和 有效的 i 的entry 交换(此时 index:i 上的 entry 就是过期的 entry), 接下来判断之前向左是否获取到了无效的key,如果有的话就调用 expungeStaleEntry 清除在左边获取的key的index,否则的话就清除当前当前 statleSlot 的值。然后调用 cleanSomeSlots 清除过期对象
  8. 如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。

    使用场景

  • 每个线程有自己的单独实例
  • 实例需要在多个方法中共享,但不希望被多线程共享

    使用 ThreadLocal 来存储 Hibernate 中 Session

    解决Java7中SimpleDateFormat 线程安全问题

    附: 1.8 ThreadLocal.java 代码

    ```java package java.lang; import java.lang.ref.*; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier;

/**

  • This class provides thread-local variables. These variables differ from
  • their normal counterparts in that each thread that accesses one (via its
  • {@code get} or {@code set} method) has its own, independently initialized
  • copy of the variable. {@code ThreadLocal} instances are typically private
  • static fields in classes that wish to associate state with a thread (e.g.,
  • a user ID or Transaction ID). *
  • For example, the class below generates unique identifiers local to each

  • thread.
  • A thread’s id is assigned the first time it invokes {@code ThreadId.get()}
  • and remains unchanged on subsequent calls.
    1.  
  • import java.util.concurrent.atomic.AtomicInteger; *
  • public class ThreadId {
  • // Atomic integer containing the next thread ID to be assigned
  • private static final AtomicInteger nextId = new AtomicInteger(0); *
  • // Thread local variable containing each thread’s ID
  • private static final ThreadLocal<Integer> threadId =
  • new ThreadLocal<Integer>() {
  • @Override protected Integer initialValue() {
  • return nextId.getAndIncrement();
  • }
  • }; *
  • // Returns the current thread’s unique ID, assigning it if necessary
  • public static int get() {
  • return threadId.get();
  • }
  • }
  • Each thread holds an implicit reference to its copy of a thread-local

  • variable as long as the thread is alive and the {@code ThreadLocal}
  • instance is accessible; after a thread goes away, all of its copies of
  • thread-local instances are subject to garbage collection (unless other
  • references to these copies exist). *
  • @author Josh Bloch and Doug Lea
  • @since 1.2 / public class ThreadLocal { /*

    • ThreadLocals rely on per-thread linear-probe hash maps attached
    • to each thread (Thread.threadLocals and
    • inheritableThreadLocals). The ThreadLocal objects act as keys,
    • searched via threadLocalHashCode. This is a custom hash code
    • (useful only within ThreadLocalMaps) that eliminates collisions
    • in the common case where consecutively constructed ThreadLocals
    • are used by the same threads, while remaining well-behaved in
    • less common cases. */ private final int threadLocalHashCode = nextHashCode();

      /**

    • The next hash code to be given out. Updated atomically. Starts at
    • zero. */ private static AtomicInteger nextHashCode = new AtomicInteger();

      /**

    • The difference between successively generated hash codes - turns
    • implicit sequential thread-local IDs into near-optimally spread
    • multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647;

      /**

    • Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }

      /**

    • Returns the current thread’s “initial value” for this
    • thread-local variable. This method will be invoked the first
    • time a thread accesses the variable with the {@link #get}
    • method, unless the thread previously invoked the {@link #set}
    • method, in which case the {@code initialValue} method will not
    • be invoked for the thread. Normally, this method is invoked at
    • most once per thread, but it may be invoked again in case of
    • subsequent invocations of {@link #remove} followed by {@link #get}. *
    • This implementation simply returns {@code null}; if the

    • programmer desires thread-local variables to have an initial
    • value other than {@code null}, {@code ThreadLocal} must be
    • subclassed, and this method overridden. Typically, an
    • anonymous inner class will be used. *
    • @return the initial value for this thread-local */ protected T initialValue() { return null; }

      /**

    • Creates a thread local variable. The initial value of the variable is
    • determined by invoking the {@code get} method on the {@code Supplier}. *
    • @param the type of the thread local’s value
    • @param supplier the supplier to be used to determine the initial value
    • @return a new thread local variable
    • @throws NullPointerException if the specified supplier is null
    • @since 1.8 */ public static ThreadLocal withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); }

      /**

    • Creates a thread local variable.
    • @see #withInitial(java.util.function.Supplier) */ public ThreadLocal() { }

      /**

    • Returns the value in the current thread’s copy of this
    • thread-local variable. If the variable has no value for the
    • current thread, it is first initialized to the value returned
    • by an invocation of the {@link #initialValue} method. *
    • @return the current thread’s value of this thread-local */ public T get() { Thread t = Thread.currentThread(); 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(); }

      /**

    • Variant of set() to establish initialValue. Used instead
    • of set() in case user has overridden the set() method. *
    • @return the initial value */ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null)

      1. map.set(this, value);

      else

      1. createMap(t, value);

      return value; }

      /**

    • Sets the current thread’s copy of this thread-local variable
    • to the specified value. Most subclasses will have no need to
    • override this method, relying solely on the {@link #initialValue}
    • method to set the values of thread-locals. *
    • @param value the value to be stored in the current thread’s copy of
    • this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }

      /**

    • Removes the current thread’s value for this thread-local
    • variable. If this thread-local variable is subsequently
    • {@linkplain #get read} by the current thread, its value will be
    • reinitialized by invoking its {@link #initialValue} method,
    • unless its value is {@linkplain #set set} by the current thread
    • in the interim. This may result in multiple invocations of the
    • {@code initialValue} method in the current thread. *
    • @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null)

      1. m.remove(this);

      }

      /**

    • Get the map associated with a ThreadLocal. Overridden in
    • InheritableThreadLocal. *
    • @param t the current thread
    • @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }

      /**

    • Create the map associated with a ThreadLocal. Overridden in
    • InheritableThreadLocal. *
    • @param t the current thread
    • @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

      /**

    • Factory method to create map of inherited thread locals.
    • Designed to be called only from Thread constructor. *
    • @param parentMap the map associated with parent thread
    • @return a map containing the parent’s inheritable bindings */ static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }

      /**

    • Method childValue is visibly defined in subclass
    • InheritableThreadLocal, but is internally defined here for the
    • sake of providing createInheritedMap factory method without
    • needing to subclass the map class in InheritableThreadLocal.
    • This technique is preferable to the alternative of embedding
    • instanceof tests in methods. */ T childValue(T parentValue) { throw new UnsupportedOperationException(); }

      /**

    • An extension of ThreadLocal that obtains its initial value from
    • the specified {@code Supplier}. */ static final class SuppliedThreadLocal extends ThreadLocal {

      private final Supplier<? extends T> supplier;

      SuppliedThreadLocal(Supplier<? extends T> supplier) {

      1. this.supplier = Objects.requireNonNull(supplier);

      }

      @Override protected T initialValue() {

      1. return supplier.get();

      } }

      /**

    • ThreadLocalMap is a customized hash map suitable only for
    • maintaining thread local values. No operations are exported
    • outside of the ThreadLocal class. The class is package private to
    • allow declaration of fields in class Thread. To help deal with
    • very large and long-lived usages, the hash table entries use
    • WeakReferences for keys. However, since reference queues are not
    • used, stale entries are guaranteed to be removed only when
    • the table starts running out of space. */ static class ThreadLocalMap {

      /**

      • The entries in this hash map extend WeakReference, using
      • its main ref field as the key (which is always a
      • ThreadLocal object). Note that null keys (i.e. entry.get()
      • == null) mean that the key is no longer referenced, so the
      • entry can be expunged from table. Such entries are referred to
      • as “stale entries” in the code that follows. / static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. / Object value;

        Entry(ThreadLocal<?> k, Object v) {

        1. super(k);
        2. value = v;

        } }

        /**

      • The initial capacity — MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16;

        /**

      • The table, resized as necessary.
      • table.length MUST always be a power of two. */ private Entry[] table;

        /**

      • The number of entries in the table. */ private int size = 0;

        /**

      • The next size value at which to resize. */ private int threshold; // Default to 0

        /**

      • Set the resize threshold to maintain at worst a 2/3 load factor. / private void setThreshold(int len) { threshold = len 2 / 3; }

        /**

      • Increment i modulo len. */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }

        /**

      • Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); }

        /**

      • Construct a new map initially containing (firstKey, firstValue).
      • ThreadLocalMaps are constructed lazily, so we only create
      • one when we have at least one entry to put in it. */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }

        /**

      • Construct a new map including all Inheritable ThreadLocals
      • from given parent map. Called only by createInheritedMap. *
      • @param parentMap the map associated with parent thread. */ private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len];

        for (int j = 0; j < len; j++) {

        1. Entry e = parentTable[j];
        2. if (e != null) {
        3. @SuppressWarnings("unchecked")
        4. ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
        5. if (key != null) {
        6. Object value = key.childValue(e.value);
        7. Entry c = new Entry(key, value);
        8. int h = key.threadLocalHashCode & (len - 1);
        9. while (table[h] != null)
        10. h = nextIndex(h, len);
        11. table[h] = c;
        12. size++;
        13. }
        14. }

        } }

        /**

      • Get the entry associated with key. This method
      • itself handles only the fast path: a direct hit of existing
      • key. It otherwise relays to getEntryAfterMiss. This is
      • designed to maximize performance for direct hits, in part
      • by making this method readily inlinable. *
      • @param key the thread local object
      • @return the entry associated with key, or null if no such */ private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key)

        1. return e;

        else

        1. return getEntryAfterMiss(key, i, e);

        }

        /**

      • Version of getEntry method for use when key is not found in
      • its direct hash slot. *
      • @param key the thread local object
      • @param i the table index for key’s hash code
      • @param e the entry at table[i]
      • @return the entry associated with key, or null if no such */ private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length;

        while (e != null) {

        1. ThreadLocal<?> k = e.get();
        2. if (k == key)
        3. return e;
        4. if (k == null)
        5. expungeStaleEntry(i);
        6. else
        7. i = nextIndex(i, len);
        8. e = tab[i];

        } return null; }

        /**

      • Set the value associated with key. *
      • @param key the thread local object
      • @param value the value to be set */ private void set(ThreadLocal<?> key, Object value) {

        // We don’t use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not.

        Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1);

        for (Entry e = tab[i];

        1. e != null;
        2. e = tab[i = nextIndex(i, len)]) {
        3. ThreadLocal<?> k = e.get();
        4. if (k == key) {
        5. e.value = value;
        6. return;
        7. }
        8. if (k == null) {
        9. replaceStaleEntry(key, value, i);
        10. return;
        11. }

        }

        tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold)

        1. rehash();

        }

        /**

      • Remove the entry for key. */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i];

        1. e != null;
        2. e = tab[i = nextIndex(i, len)]) {
        3. if (e.get() == key) {
        4. e.clear();
        5. expungeStaleEntry(i);
        6. return;
        7. }

        } }

        /**

      • Replace a stale entry encountered during a set operation
      • with an entry for the specified key. The value passed in
      • the value parameter is stored in the entry, whether or not
      • an entry already exists for the specified key. *
      • As a side effect, this method expunges all stale entries in the
      • “run” containing the stale entry. (A run is a sequence of entries
      • between two null slots.) *
      • @param key the key
      • @param value the value to be associated with key
      • @param staleSlot index of the first stale entry encountered while
      • searching for key. */ private void replaceStaleEntry(ThreadLocal<?> key, Object value,

        1. int staleSlot) {

        Entry[] tab = table; int len = tab.length; Entry e;

        // Back up to check for prior stale entry in current run. // We clean out whole runs at a time to avoid continual // incremental rehashing due to garbage collector freeing // up refs in bunches (i.e., whenever the collector runs). int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i;

        // Find either the key or trailing null slot of run, whichever // occurs first for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get();

        // If we find key, then we need to swap it // with the stale entry to maintain hash table order. // The newly stale slot, or any other stale slot // encountered above it, can then be sent to expungeStaleEntry // to remove or rehash all of the other entries in run. if (k == key) { e.value = value;

        tab[i] = tab[staleSlot]; tab[staleSlot] = e;

        // Start expunge at preceding stale entry if it exists if (slotToExpunge == staleSlot)

        1. slotToExpunge = i;

        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; }

        // If we didn’t find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; }

        // If key not found, put new entry in stale slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value);

        // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }

        /**

      • Expunge a stale entry by rehashing any possibly colliding entries
      • lying between staleSlot and the next null slot. This also expunges
      • any other stale entries encountered before the trailing null. See
      • Knuth, Section 6.4 *
      • @param staleSlot index of slot known to have null key
      • @return the index of the next null slot after staleSlot
      • (all between staleSlot and this slot will have been checked
      • for expunging). */ private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length;

        // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size—;

        // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len);

        1. (e = tab[i]) != null;
        2. i = nextIndex(i, len)) {
        3. ThreadLocal<?> k = e.get();
        4. if (k == null) {
        5. e.value = null;
        6. tab[i] = null;
        7. size--;
        8. } else {
        9. int h = k.threadLocalHashCode & (len - 1);
        10. if (h != i) {
        11. tab[i] = null;
        12. // Unlike Knuth 6.4 Algorithm R, we must scan until
        13. // null because multiple entries could have been stale.
        14. while (tab[h] != null)
        15. h = nextIndex(h, len);
        16. tab[h] = e;
        17. }
        18. }

        } return i; }

        /**

      • Heuristically scan some cells looking for stale entries.
      • This is invoked when either a new element is added, or
      • another stale one has been expunged. It performs a
      • logarithmic number of scans, as a balance between no
      • scanning (fast but retains garbage) and a number of scans
      • proportional to number of elements, that would find all
      • garbage but would cause some insertions to take O(n) time. *
      • @param i a position known NOT to hold a stale entry. The
      • scan starts at the element after i. *
      • @param n scan control: {@code log2(n)} cells are scanned,
      • unless a stale entry is found, in which case
      • {@code log2(table.length)-1} additional cells are scanned.
      • When called from insertions, this parameter is the number
      • of elements, but when from replaceStaleEntry, it is the
      • table length. (Note: all this could be changed to be either
      • more or less aggressive by weighting n instead of just
      • using straight log n. But this version is simple, fast, and
      • seems to work well.) *
      • @return true if any stale entries have been removed. */ private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do {

        1. i = nextIndex(i, len);
        2. Entry e = tab[i];
        3. if (e != null && e.get() == null) {
        4. n = len;
        5. removed = true;
        6. i = expungeStaleEntry(i);
        7. }

        } while ( (n >>>= 1) != 0); return removed; }

        /**

      • Re-pack and/or re-size the table. First scan the entire
      • table removing stale entries. If this doesn’t sufficiently
      • shrink the size of the table, double the table size. */ private void rehash() { expungeStaleEntries();

        // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4)

        1. resize();

        }

        /**

      • Double the capacity of the table. / private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen 2; Entry[] newTab = new Entry[newLen]; int count = 0;

        for (int j = 0; j < oldLen; ++j) {

        1. Entry e = oldTab[j];
        2. if (e != null) {
        3. ThreadLocal<?> k = e.get();
        4. if (k == null) {
        5. e.value = null; // Help the GC
        6. } else {
        7. int h = k.threadLocalHashCode & (newLen - 1);
        8. while (newTab[h] != null)
        9. h = nextIndex(h, newLen);
        10. newTab[h] = e;
        11. count++;
        12. }
        13. }

        }

        setThreshold(newLen); size = count; table = newTab; }

        /**

      • Expunge all stale entries in the table. */ private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) {
        1. Entry e = tab[j];
        2. if (e != null && e.get() == null)
        3. expungeStaleEntry(j);
        } } } } ```