ThreadLocal 源码

news2025/6/10 15:20:25

ThreadLocal 源码

此类提供线程局部变量。这些变量不同于它们的普通对应物,因为每个访问一个线程局部变量的线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些类希望将状态与线程关联(例如,用户 ID 或事务 ID)。

例如,下面的类生成每个线程本地的唯一标识符。线程的id在第一次调用ThreadId. get() 时被分配,并且在后续调用中保持不变。

  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();
      }
  }

每个线程都持有对其线程局部变量副本的隐式引用,只要线程处于活动状态并且ThreadLocal实例是可访问的; 线程消失后,线程本地实例的所有副本都受到垃圾回收 (除非存在对这些副本的其他引用)。

public class ThreadLocal<T> {
    /**
     * ThreadLocals依赖于附加到每个线程的每线程线性探针哈希映射 (thread. threadLocals和inheritableThreadLocals)。
     * ThreadLocal对象充当键,通过threadLocalHashCode搜索。
     * 这是一个自定义哈希代码 (仅在ThreadLocalMaps中有用),它消除了在相同线程使用连续构造的ThreadLocals的常见情况下的冲突,同时在不太常见的情况下保持良好的行为。
	 */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * 下一个要给出的哈希码。原子更新。从零开始。
     */
    private static AtomicInteger nextHashCode = new AtomicInteger();

    /**
     * 连续生成的哈希码之间的差异
     * 它将隐式的顺序线程本地 ID 转换为具有良好分布特性的乘法哈希值
     *
     * 为何使用 0x61c88647:
     *   这个十六进制常量是黄金分割比例((sqrt(5)-1)/2 ≈ 0.618)的一个近似值。
     *   在哈希算法中,它能够提供良好的随机分布特性,有助于减少哈希冲突。
     *   特别适用于大小为 2 的幂的哈希表,因为可以通过位运算快速取模。
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * 返回下一个哈希代码。
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    /**
     * 返回当前线程对该线程本地变量的“初始值”。
     * 当线程第一次通过 {@link #get} 方法访问该变量时,此方法会被调用,
     * 除非线程此前已经调用了 {@link #set} 方法,在这种情况下,不会为此线程调用 {@code initialValue} 方法。
     * 通常,每个线程最多调用一次该方法,但在后续调用 {@link #remove} 后再调用 {@link #get} 的情况下,可能会再次调用。
     *
     * <p>该实现默认返回 {@code null};如果希望线程本地变量具有非 {@code null} 的初始值,
     * 则必须继承 {@code ThreadLocal} 类并重写此方法。通常会使用匿名内部类来实现。
     *
     * @return 该线程本地变量的初始值
     */
    protected T initialValue() {
        return null;
    }

    /**
     * 创建一个线程本地变量。该变量的初始值由调用指定 {@code Supplier} 的 {@code get} 方法来确定。
     *
     * @param <S> 线程本地变量的值类型
     * @param supplier 用于确定初始值的供给函数(supplier),不能为空
     * @return 一个新的线程本地变量实例
     * @throws NullPointerException 如果指定的 supplier 为 null
     * @since 1.8
     */
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

    /**
     * 创建一个线程本地变量。
     *
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

    /**
     * 返回当前线程中该线程本地变量的副本的值。
     * 如果该变量在当前线程中尚无值,则会先使用 {@link #initialValue} 方法的返回值进行初始化。
     *
     * @return 当前线程中该线程本地变量的值
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * 用于设置初始值的一个 set() 方法变体。
     * 在用户重写了 set() 方法的情况下,使用此方法替代原生 set()。
     *
     * 用于在首次获取(get())一个 ThreadLocal 变量时,建立该变量的初始值。它会调用 initialValue() 方法来获取初始值,并将这个值存入线程本地的 ThreadLocalMap 中。
     *
     * 为什么不用 set()?
     * 因为 set() 是一个公开方法,用户可能对其进行了重写(自定义了行为)。为了防止初始化过程受到用户自定义 set() 的影响,Java 使用了这个专门的 setInitialValue() 方法来进行初始值的设置。
     *
     * @return 初始值
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    /**
     * 将当前线程中该线程本地变量的副本设置为指定的值。
     * 大多数子类不需要重写此方法,而是完全依赖于 {@link #initialValue} 方法来设定线程本地变量的值。
     *
     * @param value 要存储在当前线程的该线程本地变量副本中的值
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * 移除当前线程中该线程本地变量的值。
     * 如果该线程本地变量之后被当前线程通过 {@linkplain #get} 方法再次读取,
     * 它的值将通过调用其 {@link #initialValue} 方法重新初始化,
     * 除非在这期间当前线程已经通过 {@linkplain #set} 方法设置了新的值。
     * 这可能导致在同一个线程中多次调用 {@code initialValue} 方法。
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    /**
     * 获取与当前 ThreadLocal 关联的线程本地变量映射表(ThreadLocalMap)。
     * 该方法在 InheritableThreadLocal 中被重写。
     *
     * @param t 当前线程
     * @return 与当前线程关联的 ThreadLocalMap
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * 创建与 ThreadLocal 关联的映射表(ThreadLocalMap)。
     * 该方法在 InheritableThreadLocal 中被重写。
     *
     * @param t 当前线程
     * @param firstValue 映射表中初始条目的值
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    /**
     * 工厂方法,用于创建继承自父线程的线程本地变量映射表。
     * 该方法设计为仅由 Thread 构造函数调用。
     *
     * @param parentMap 父线程关联的 ThreadLocalMap
     * @return 一个包含父线程可继承绑定的新映射表
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

    /**
     * 该方法在子类 InheritableThreadLocal 中被显式定义,
     * 但在这里内部声明,目的是为了在不继承 map 类的情况下,
     * 提供 createInheritedMap 工厂方法。
     *
     * 这种设计优于在方法中嵌入 instanceof 检查的方式。
     */
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

    /**
     * 一个继承自 ThreadLocal 的扩展类,其初始值来源于
     * 指定的 {@code Supplier} 函数式接口。
     */
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        private final Supplier<? extends T> supplier;

        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }

    /**
     * ThreadLocalMap 是一个专门定制的哈希映射表,仅用于维护线程本地变量(thread-local values)。
     * 该类的所有操作都不会暴露到 ThreadLocal 类之外。
     * 该类被定义为包私有(package private),以便允许在 Thread 类中声明其字段。
     *
     * 为了应对大量且长期存活的使用场景,哈希表中的键(key)采用弱引用(WeakReference)的形式。
     * 然而,由于没有使用引用队列(reference queues),
     * 所以无效的条目(stale entries)只有当哈希表空间不足时才会被真正清除。
     */
    static class ThreadLocalMap {

        /**
         * 这个哈希映射中的每个条目(Entry)都继承自 WeakReference(弱引用),
         * 使用其主引用字段作为键(这个键始终是一个 ThreadLocal 对象)。
         *
         * 注意:如果键为 null(即 entry.get() == null),
         * 表示该键已经被垃圾回收,因此该条目可以被从表中清除。
         * 在后续代码中,这类条目被称为 "stale entries"(过期条目)。
         */

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** 与当前 ThreadLocal 关联的值。 */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * 初始容量 —— 必须是 2 的幂(power of two)。
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * 哈希表,根据需要进行扩容或缩容。
         * table.length 的值必须始终是 2 的幂次方。
         */
        private Entry[] table;

        /**
         * 哈希表中条目(键值对)的数量。
         */
        private int size = 0;

        /**
         * 下一个需要扩容时的尺寸阈值。
         */
        private int threshold; // Default to 0

        /**
         * 设置扩容阈值,以保证最坏情况下负载因子不超过 2/3。
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * 将 i 按 len 取模后递增。
         *
         * 更通俗地解释,该方法用于在哈希表索引中进行线性探测时,将当前索引 i 递增,并通过取模确保其不会超出数组长度 len 的范围。
         * 如果递增后的值等于数组长度,则回到 0,实现一个环形数组的索引遍历。
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * 将 i 按 len 取模后递减。
         *
         * 该方法用于在哈希表索引中进行反向线性探测时,将当前索引 i 递减,并通过取模确保其不会小于 0,从而实现对环形数组的索引安全访问。
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * 构造一个初始包含 (firstKey, firstValue) 的新映射表(ThreadLocalMap)。
         * ThreadLocalMap 是惰性构造的,所以我们只在有至少一个条目需要插入时才会真正创建它。
         */
        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);
        }

        /**
         * 构造一个新映射表(ThreadLocalMap),包含给定父映射表中的所有可继承的 InheritableThreadLocal。
         * 该方法仅由 createInheritedMap 调用。
         *
         * @param parentMap 与父线程关联的映射表(ThreadLocalMap)。
         */
        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++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

        /**
         * 获取与指定键(key)关联的条目(Entry)。
         * 该方法仅处理快速路径:直接命中已存在的键。
         * 如果未命中,则会转交给 getEntryAfterMiss 方法处理。
         * 这样设计是为了通过使该方法易于内联,从而提升直接命中的性能。
         *
         * @param key 线程本地对象(ThreadLocal)
         * @return 与键关联的条目,如果不存在则返回 null
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * 当在直接哈希槽中未找到键时,使用的 getEntry 方法变体。
         *
         * @param key 线程本地对象(ThreadLocal)
         * @param i 键的哈希码对应的表索引
         * @param e 表中索引为 i 的条目(Entry)
         * @return 与键关联的条目(Entry),如果不存在则返回 null
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * 设置与指定键(key)关联的值(value)。
         *
         * @param key 线程本地对象(ThreadLocal)
         * @param value 要设置的值
         */
        private void set(ThreadLocal<?> key, Object value) {

            // 我们不像 get() 方法那样使用快速路径,因为使用 set() 方法创建新条目的情况
            // 至少和替换已有条目的情况一样常见。在这种情况下,快速路径往往会频繁失败。

            // 这段注释说明了为何 set() 方法在实现上没有采用像 get() 一样的“快速路径”优化。
            // 在 get() 中,大多数情况下是查找已存在的键,因此可以使用快速路径(直接命中)来提升性能。
            // 但在 set() 中,插入新条目(create)和替换旧条目(replace)的频率差不多,所以如果强行用类似 get() 的快速路径,会导致很多情况下需要回退到慢速路径(如处理哈希冲突、清理过期条目等),反而影响整体效率。
            // 因此,set() 方法选择统一走一个更通用的逻辑,避免单独设置“快速路径”。

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

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

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

        /**
         * 移除与指定键(key)关联的条目。
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

        /**
         * 在 set 操作中遇到过期条目时,用指定键的新条目替换它。
         * 无论是否已存在该键对应的条目,传入的 value 都会被存储到条目中。
         *
         * 作为副作用,该方法会清除包含该过期条目的“运行段(run)”中的所有过期条目。
         * (一个“运行段”是指两个 null 条目之间的连续条目序列。)
         *
         * 这个注释说明了 replaceStaleEntry 方法的作用是处理哈希冲突和过期条目的一种机制:
         * 当在 set() 操作中发现某个条目的键为 null(即已被垃圾回收),就会调用此方法进行替换和清理。
         * 它不仅会将新值插入到该过期条目的位置,还会顺带清理当前“运行段”中相邻的其他过期条目(stale entries)。
         * “运行段(run)”指的是哈希表中两个 null 条目之间的连续非空条目区间。
         *
         * @param key 要设置值的键(ThreadLocal 对象)
         * @param value 要与键关联的值
         * @param staleSlot 在查找过程中遇到的第一个过期条目的索引位置
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // 向前查找当前“运行段”中是否存在之前的过期条目。
            // 我们一次性清理整个“运行段”,以避免因垃圾回收器成批释放引用而导致持续的
            // 增量再哈希(rehashing)。(即,每次垃圾回收器运行时)
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // 查找 key 或者运行段(run)中的尾随 null 槽位,以先出现者为准
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // 如果我们找到了该键,则需要将其与过期条目交换位置,
                // 以保持哈希表的顺序。
                // 新产生的过期槽位,或者在其上方遇到的其他过期槽位,
                // 可以被发送到 expungeStaleEntry 方法中,
                // 用于移除或重新哈希“运行段”中的其他条目。
                if (k == key) {
                    e.value = value;

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

                    // 如果存在前面的过期条目,则从该条目开始清理(expunge)
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // 如果在向前扫描(backward scan)过程中没有找到过期条目,
                // 那么在查找键的过程中遇到的第一个过期条目,
                // 就是当前“运行段”中仍然存在的第一个过期条目。
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // 如果未找到键,则将新条目插入到过期槽位中
            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);
        }

        /**
         * 通过重新哈希来清除一个过期条目(stale entry),该过期条目位于 staleSlot 和下一个 null 槽位之间。
         * 在此过程中也会一并清除在 trailing null 之前遇到的其他过期条目。
         * 参见 Knuth 第 6.4 节相关算法。
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // 清除位于 staleSlot 位置的过期条目
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // 重新哈希直到遇到 null 条目为止
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // 与 Knuth 6.4 的 Algorithm R 不同,我们必须扫描到 null 槽位为止,
                        // 因为可能有多个条目是过期的(stale entries)。
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

        /**
         * 启发式地扫描一些槽位(cells),寻找并清理过期条目(stale entries)。
         * 这个方法通常在以下两种情况下被调用:
         *   1. 插入一个新的元素之后;
         *   2. 清除一个过期条目之后。
         *
         * 它会执行对数级别的扫描次数(logarithmic number of scans),
         * 目的是在性能和内存回收之间取得一种平衡:
         *
         * - 如果完全不进行扫描(no scanning):
         *   插入操作会非常快,但会导致内存中保留大量“垃圾”(即 stale entries);
         *
         * - 如果扫描所有元素(full scanning):
         *   能够找到并清除所有的“垃圾”,但这会让某些插入操作的时间复杂度变成 O(n);
         *
         * 所以我们采用折中策略,只扫描一部分槽位,既不会太慢,又能回收部分垃圾数据。
         */
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

        /**
         * 重新整理(紧凑化)和/或扩容哈希表。
         * 首先扫描整个哈希表,清除所有过期条目(stale entries)。
         * 如果仅清除过期条目不足以显著缩小表的大小,则将哈希表容量加倍(扩容)。
         */
        private void rehash() {
            expungeStaleEntries();

            // 使用较低的阈值来触发扩容,以避免迟滞现象(hysteresis)
            if (size >= threshold - threshold / 4)
                resize();
        }

        /**
         * 将哈希表的容量翻倍(扩容)
         */
        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) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

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

        /**
         * 清除表中的所有过期条目(stale entries)
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }
}

InheritableThreadLocal 源码

/**
 * 该类扩展了 <tt>ThreadLocal</tt> 以提供从父线程到子线程的值的继承:
 * 当创建子线程时,子线程会接收所有继承的线程局部变量的初始值,
 * 这些变量在父线程中有值。通常情况下,子线程的值会与父线程的值相同;
 * 然而,通过在此类中重写 <tt>childValue</tt> 方法,可以将子线程的值设为父线程值的任意函数。
 *
 * <p>继承的线程局部变量用于维护必须自动传输到创建的任何子线程的每个线程属性(例如,用户ID、事务ID),
 * 优先于使用普通的线程局部变量。
 *
 * @author  Josh Bloch 和 Doug Lea
 * @see     ThreadLocal
 * @since   1.2
 */

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * 计算继承的线程局部变量的子线程初始值,作为父线程值的函数。
     * 在子线程创建时,从父线程调用该方法,子线程启动之前。
     * <p>
     * 该方法仅返回其输入参数,如果需要不同的行为,应重写该方法。
     *
     * @param parentValue 父线程的值
     * @return 子线程的初始值
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * 获取与线程局部变量关联的映射。
     *
     * @param t 当前线程
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * 创建与线程局部变量关联的映射。
     *
     * @param t 当前线程
     * @param firstValue 表的初始条目的值。
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal 使用场景

日志追踪、权限上下文传递、事务传播等场景下使用 InheritableThreadLocal 在父子线程之间传递上下文信息。也就是 InheritableThreadLocal 适用于需要子线程继承父线程上下文状态的场景,避免显式传参。

InheritableThreadLocal<String> traceIdHolder = new InheritableThreadLocal<>();

// 父线程设置 traceId
traceIdHolder.set("abc123");

// 子线程可直接获取该值
new Thread(() -> {
    System.out.println(traceIdHolder.get()); // 输出:abc123
}).start();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2406761.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

rm视觉学习1-自瞄部分

首先先感谢中南大学的开源&#xff0c;提供了很全面的思路&#xff0c;减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接&#xff1a;https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架&#xff1a; 代码框架结构&#xff1a;readme有…

高分辨率图像合成归一化流扩展

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 1 摘要 我们提出了STARFlow&#xff0c;一种基于归一化流的可扩展生成模型&#xff0c;它在高分辨率图像合成方面取得了强大的性能。STARFlow的主要构建块是Transformer自回归流&#xff08;TARFlow&am…

算法—栈系列

一&#xff1a;删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…

leetcode73-矩阵置零

leetcode 73 思路 记录 0 元素的位置&#xff1a;遍历整个矩阵&#xff0c;找出所有值为 0 的元素&#xff0c;并将它们的坐标记录在数组zeroPosition中置零操作&#xff1a;遍历记录的所有 0 元素位置&#xff0c;将每个位置对应的行和列的所有元素置为 0 具体步骤 初始化…

向量几何的二元性:叉乘模长与内积投影的深层联系

在数学与物理的空间世界中&#xff0c;向量运算构成了理解几何结构的基石。叉乘&#xff08;外积&#xff09;与点积&#xff08;内积&#xff09;作为向量代数的两大支柱&#xff0c;表面上呈现出截然不同的几何意义与代数形式&#xff0c;却在深层次上揭示了向量间相互作用的…

归并排序:分治思想的高效排序

目录 基本原理 流程图解 实现方法 递归实现 非递归实现 演示过程 时间复杂度 基本原理 归并排序(Merge Sort)是一种基于分治思想的排序算法&#xff0c;由约翰冯诺伊曼在1945年提出。其核心思想包括&#xff1a; 分割(Divide)&#xff1a;将待排序数组递归地分成两个子…

Xcode 16 集成 cocoapods 报错

基于 Xcode 16 新建工程项目&#xff0c;集成 cocoapods 执行 pod init 报错 ### Error RuntimeError - PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup from attributes: {"isa">"PBXFileSystemSynchro…

Mac flutter环境搭建

一、下载flutter sdk 制作 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 1、查看mac电脑处理器选择sdk 2、解压 unzip ~/Downloads/flutter_macos_arm64_3.32.2-stable.zip \ -d ~/development/ 3、添加环境变量 命令行打开配置环境变量文件 ope…

[拓扑优化] 1.概述

常见的拓扑优化方法有&#xff1a;均匀化法、变密度法、渐进结构优化法、水平集法、移动可变形组件法等。 常见的数值计算方法有&#xff1a;有限元法、有限差分法、边界元法、离散元法、无网格法、扩展有限元法、等几何分析等。 将上述数值计算方法与拓扑优化方法结合&#…

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…

门静脉高压——表现

一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构&#xff1a;由肠系膜上静脉和脾静脉汇合构成&#xff0c;是肝脏血液供应的主要来源。淤血后果&#xff1a;门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血&#xff0c;引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …

【51单片机】4. 模块化编程与LCD1602Debug

1. 什么是模块化编程 传统编程会将所有函数放在main.c中&#xff0c;如果使用的模块多&#xff0c;一个文件内会有很多代码&#xff0c;不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里&#xff0c;在.h文件里提供外部可调用函数声明&#xff0c;其他.c文…

DeepSeek越强,Kimi越慌?

被DeepSeek吊打的Kimi&#xff0c;还有多少人在用&#xff1f; 去年&#xff0c;月之暗面创始人杨植麟别提有多风光了。90后清华学霸&#xff0c;国产大模型六小虎之一&#xff0c;手握十几亿美金的融资。旗下的AI助手Kimi烧钱如流水&#xff0c;单月光是投流就花费2个亿。 疯…

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)

目录 &#x1f50d; 若用递归计算每一项&#xff0c;会发生什么&#xff1f; Horners Rule&#xff08;霍纳法则&#xff09; 第一步&#xff1a;我们从最原始的泰勒公式出发 第二步&#xff1a;从形式上重新观察展开式 &#x1f31f; 第三步&#xff1a;引出霍纳法则&…

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor

1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…

ArcGIS Pro+ArcGIS给你的地图加上北回归线!

今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等&#xff0c;设置经线、纬线都以10间隔显示。 2、需要插入背会归线&#xf…

MySQL体系架构解析(三):MySQL目录与启动配置全解析

MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录&#xff0c;这个目录下存放着许多可执行文件。与其他系统的可执行文件类似&#xff0c;这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中&#xff0c;用…

小智AI+MCP

什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析&#xff1a;AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github&#xff1a;https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…

DAY 45 超大力王爱学Python

来自超大力王的友情提示&#xff1a;在用tensordoard的时候一定一定要用绝对位置&#xff0c;例如&#xff1a;tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾&#xff1a; tensorboard的发展历史和原理tens…