Java-并发编程--ThreadLocal、InheritableThreadLocal

1.ThreadLocal 作用

作用:为变量在线程中都创建副本,线程可访问自己内部的副本变量。该类提供了线程局部 (thread-local) 变量,访问这个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本

原理:每个线程都有一个ThreadLocalMap类型变量 threadLocals。ThreadLocal的set()会在threadLocals中保存以ThreadLocal对象为key,以保存的变量为value的值,get()会获取该值

建议:

  • 将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
  • 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

2.ThreadLocal继承关系

3.源码走读

3.1.ThreadLocal.java
public class ThreadLocal<T> {
    //**每一个实例都有一个唯一的threadLocalHashCode,值为上一个实例的值加上0x61c88647
    //**作用是为了让哈希码能均匀的分布在2的N次方的数组里
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode = new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    //**返回此线程局部变量的当前线程的“初始值”
    //**线程第一次使用get()方法时调用此方法,如果线程之前调用了set(T)方法,则不会对该线程再调用该方法
    //**通常,此方法对每个线程最多调用一次,但调用了remove(),则会再次调用此方法
    //**默认返回null,如果希望返回其它值,则须创建子类,并重写此方法,通常将使用匿名内部类完成此操作 
    protected T initialValue() {
        return null;
    }

    //**在java8,使用函数式编程的方式设置并返回当前线程变量的初始值,与上个方法功能相同
    //**示例:ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "test");
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        //**返回SuppliedThreadLocal对象,SuppliedThreadLocal是ThreadLocal的子类,
        //**重写的initialValue方法调用supplier的get方法做为当前线程变量的初始值
        return new SuppliedThreadLocal<>(supplier);
    }

    //**返回此线程局部变量的当前线程副本中的值,如果变量没有用于当前线程的值,则返回initialValue()的值
    public T get() {
        //**获取当前线程的实例
        Thread t = Thread.currentThread();
        //**获取当前线程中的ThreadLocalMap类型变量threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //**从threadLocals中获取以this为key的Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            //**如果Entry对象不为空,则返回它的value
            if (e != null) {
                @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                return result;
            }
        }
        //**如果threadLocals对象不为空或者Entry为空,则调用setInitialValue进行初始化
        return setInitialValue();
    }

    //**使用initialValue()的值初始化线程局部变量
    private T setInitialValue() {
        //**获取线程局部变量的初始值,默认为null
        T value = initialValue();
        //**获取当前线程的实例
        Thread t = Thread.currentThread();
        //**获取当前线程中的ThreadLocalMap类型变量threadLocals
        ThreadLocalMap map = getMap(t);
        //**如果threadLocals不为空,设置以this为key,以value为值的Entry对象
        if (map != null)
            map.set(this, value);
            //**如果threadLocals为空,则进行初始化,并设置以this为key,以value为值的Entry对象
        else
            createMap(t, value);
        return 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);
    }

    //**移除此线程局部变量当前线程的值,如果随后调用get()方法,且没有调用set()设置值,则将调用initialValue()重新初始化值
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            m.remove(this);
    }

    //**从线程实例中获取threadLocals对象
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    //**初始化线程t的threadLocals对象,并设置以this为key,以firstValue为值的Entry对象
    void createMap(Thread t, T firstValue) {
                   t.threadLocals = new ThreadLocalMap(this, firstValue);
                   }

                   //**根据主线程中的ThreadLocalMap对象创建子线程的ThreadLocalMap对象
                   static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
                   return new ThreadLocalMap(parentMap);
                   }

                   //**ThreadLocal对象不支持,在InheritableThreadLocal中实现
                   T childValue(T parentValue) {
                   throw new UnsupportedOperationException();
                   }
                   }
3.2.SuppliedThreadLocal.java
  • ithInitial方法使用Supplier对象创建SuppliedThreadLocal对象
  • 作用是为了在java8,支持使用函数式编程的方式设置并返回当前线程变量的初始值
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
    //**重写的initialValue方法,调用supplier的get方法做为当前线程变量的初始值
    protected T initialValue() {
        return supplier.get();
    }
}
3.4.ThreadLocalMap.java
作用:

ThreadLocalMap是ThreadLocal的内部类。存放以ThreadLocal变量为key,以保存的变量为value的键值对

原理:
  • ThreadLocalMap内部以Entry[]做为存储,原始长度默认为16,当元素个数达到扩容阀值(数组长度的3/4)-扩容阀值/4,则自动扩容,扩容到上次长度的2倍。Entry[]的长度必须是2的倍数
  • Entry[]存储元素并不是按索引顺序存储,而是根据ThreadLocal进行计算存储位置,这样能实现根据ThreadLocal都能快速定位键值对,而不用遍历数组的每个元素
  • 计算方法:ThreadLocal.threadLocalHashCode & (Entry[].length - 1)计算,ThreadLocal每一个实例都有一个唯一的threadLocalHashCode,值为上一个实例的值加上0x61c88647,该算法可以生成均匀的分布在2的N次方数组里的下标
  • 如果计算的存储位置已经有元素,则会存放到下一个索引的位置,ThreadLocalMap会清理过期数据,并重新根据计算的存储位置重置,以保证尽可能减少和纠正此类问题
static class ThreadLocalMap {
    //**存放单个键值对的对象
    //**弱引用: 如果某个对象只有弱引用,那么gc会立即回收
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

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

    //**默认初始化的大小,必须是2的倍数
    private static final int INITIAL_CAPACITY = 16;

    //**真正存储数据的数组,长度必须是2的倍数
    private Entry[] table;

    //**ThreadLocalMap的大小,即上述Entry[]中存放元素的个数
    private int size = 0;

    //**自动扩容的阀值
    private int threshold; // Default to 0

    //**设置自动扩容的阀值,为设定长度的2/3
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

    //**下一个索引
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    //**上一个索引
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }

    //**创建ThreadLocalMap,并设置第一个键值对
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        //**根据默认初始化的大小初始化Entry[]
        table = new Entry[INITIAL_CAPACITY];
        //**根据threadlocal对象的threadLocalHashCode和Entry[]数组的长度计算存放的位置
        //**该算法可以生成均匀的分布在2的N次方数组里的下标
        //**每个键值对并不是按顺序存放Entry[]里面
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        //**把Entry对象放到指定位置
        table[i] = new Entry(firstKey, firstValue);
        //**设置ThreadLocalMap的大小,即Entry[]中存放元素的个数
        size = 1;
        //**设置自动扩容的阀值
        setThreshold(INITIAL_CAPACITY);
    }

    //**根据parentMap创建另一个parentMap,使用InheritableThreadLocal时,创建子线程时会调用
    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) {
                    //**调用InheritableThreadLocal的childValue方法处理保存的对象
                    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++;
                }
            }
        }
    }

    //**根据threadlocal获取Entry对象
    private Entry getEntry(ThreadLocal<?> key) {
        //**计算下标
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        //**如果对象存在,且key一样,则返回
        if (e != null && e.get() == key)
            return e;
        else  //**否则从指定索引的下一个索引开始查找
            return getEntryAfterMiss(key, i, e);
    }

    //**没有直接命中,则指定索引的下一个索引开始查找
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;

            //**从指定索引开始遍历,直到数据为null
            while (e != null) {
            ThreadLocal<?> k = e.get();
            //**如果数据存在则返回
            if (k == key)
            return e;
            //**threadlocal对象为空,删除过期数据
            if (k == null)
            //**删除过期数据
            expungeStaleEntry(i);
            //**i为下一个索引
            else
            i = nextIndex(i, len);
            //**e为下一个索引的值
            e = tab[i];
            }
            //**没有数据不存在则返回null
            return null;
            }

            //**根据threadlocal对象设置value
            private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            //**计算存放的索引
            int i = key.threadLocalHashCode & (len-1);

            //**从指定索引开始遍历Entry[],直到数据为null
            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            //**如果数据存在,则直接返回
            if (k == key) {
            e.value = value;
            return;
            }
            //**如果key为空,则替换当前索引的数据,并返回
            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);
            //**从指定的索引开始遍历Entry[],直到数据为null
            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            //**如果指定key存在,则删除指定数据
            if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
            }
            }
            }

            //**替换指定索引的过期数据的
            private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            //**从指定索引往前找,找到过期数据的索引
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len))
            if (e.get() == null)
            slotToExpunge = i;

            //**从指定索引往后找
            for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();

            //**如果是数据的key等于指定的key
            if (k == key) {
            //**替换它的value
            e.value = value;

            //**把它的位置和指定索引的位置互换(把数据替换到计算索引的位置)
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            //**如果过期数据的的索引等于指定索引,则过期数据的索引为互换后的新索引
            if (slotToExpunge == staleSlot)
            slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
            }

            //**过期数据的索引
            if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
            }

            //**如果指定数据不存在,则创建新的数据
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            //**如果有过时的条目,则清理
            if (slotToExpunge != staleSlot)
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            }

            //**删除指定索引的过期数据,并返回数据为null的索引
            private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            //**指定索引的数据置为null,数据减一(删除指定数据)
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            Entry e;
            int i;
            //**从指定的索引的下一个数据开始循环遍历Entry[]数组,直到遇到null值
            for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            //**如果key为空,Entry置为空,数据减一(删除指定数据)
            if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
            } else {
            //**重新计算存放的索引
            int h = k.threadLocalHashCode & (len - 1);
            //**如果新索引不等于原索引,则原索引数据置为null
            if (h != i) {
            tab[i] = null;

            //**如果新的存放的索引有数据,则存放到新索引的下一个索引,直到没有数据为止
            while (tab[h] != null)
            h = nextIndex(h, len);
            tab[h] = e;
            }
            }
            }
            //**返回数据为null的索引
            return i;
            }

            //**从指定索引开始清理数据
            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;
            }

            //**删除过期数据并扩容
            private void rehash() {
            //**删除所有的过期数据
            expungeStaleEntries();

            //**数据量 >= 扩容阀值 - 扩容阀值 / 4,则扩容
            if (size >= threshold - threshold / 4)
            resize();
            }

            //**扩容
            private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            //**扩容为原来的2倍
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            //**把旧数据存放在新的Entry[]中
            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;
            }

            //**删除所有的过期数据
            private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
            Entry e = tab[j];
            //**如果Entry不为空并且key为空(threadlocal对象为null)则为过期数据
            if (e != null && e.get() == null)
            expungeStaleEntry(j);
            }
            }
            }
3.5 Thread 、ThreadLocal、ThreadLocalMap关系图

Thread1 和Thread2 线程中的ThreadLocal 是相同的话,那么ThreadLocalMap 中Entry 下标位置都是 ThreadLocal 的hashcode & (len-1)的位置如不出现hash冲突的话则都是相同的。

3.6 InheritThreadLocal详解

原理和解析:

  • 每个线程都还有另外一个ThreadLocalMap类型变量inheritableThreadLocals
  • InheritableThreadLocal重写了getMap和createMap方法,维护的不在是threadLocals,而是inheritableThreadLocals
  • 当主线程创建一个子线程的时候,会判断主线程的inheritableThreadLocals是否为空
  • 如果不为空,则会把inheritableThreadLocals的值传给子线程的inheritableThreadLocals,传送的逻辑是childValue实现的
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc) {
    ...
    //**获取主线程的实例
    Thread parent = currentThread();
    ...
    //**如果主线的inheritableThreadLocals不为空
    if (parent.inheritableThreadLocals != null)
        //**根据主线程的inheritableThreadLocals创建子线程的inheritableThreadLocals
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        ...
}

注意:

  • 因为传送逻辑是在创建子线程的时候完成的,子线程创建后,主线程在修改InheritableThreadLocal变量的值,是无法传给子线程的
  • 创建子线程完成后,原则上子线程和父线程中InheritableThreadLocal变量的值在没有关联,各自调用set/get/remove都只影响本线程中的值
  • 如果InheritableThreadLocal变量的值是引用类型,通过get方法获取到对象后,直接修改了该对象的属性,则父线程和子线程都会受影响

InheritableThreadLocal类重写了ThreadLocal的3个函数:

	/**
     * 该函数在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }
	/**
     * 由于重写了getMap,操作InheritableThreadLocal时,
     * 将只影响Thread类中的inheritableThreadLocals变量,
     * 与threadLocals变量不再有关系
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
	/**
     * 类似于getMap,操作InheritableThreadLocal时,
     * 将只影响Thread类中的inheritableThreadLocals变量,
     * 与threadLocals变量不再有关系
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

线程间传值实现原理

public class Thread implements Runnable {
   ......(其他源码)
    /* 
     * 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
     * 主要用于父子线程间ThreadLocal变量的传递
     * 本文主要讨论的就是这个ThreadLocalMap
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    ......(其他源码)
}

Thread类中包含 threadLocals 和 inheritableThreadLocals 两个变量,其中inheritableThreadLocals 即主要存储可自动向子线程中传递的ThreadLocal.ThreadLocalMap。
接下来看一下父线程创建子线程的流程,我们从最简单的方式说起:
用户创建Thread
Thread thread = new Thread();

   /**
    * Allocates a new {@code Thread} object. This constructor has the same
    * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
    * {@code (null, null, gname)}, where {@code gname} is a newly generated
    * name. Automatically generated names are of the form
    * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
    */
   public Thread() {
       init(null, null, "Thread-" + nextThreadNum(), 0);
   }

	/**
     * 默认情况下,设置inheritThreadLocals可传递
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

	/**
     * 初始化一个线程.
     * 此函数有两处调用,
     * 1、上面的 init(),不传AccessControlContext,inheritThreadLocals=true
     * 2、传递AccessControlContext,inheritThreadLocals=false
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        ......(其他代码)

        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

        ......(其他代码)
    }

可以看到,采用默认方式产生子线程时,inheritThreadLocals=true;若此时父线程inheritableThreadLocals不为空,则将父线程inheritableThreadLocals传递至子线程

ThreadLocal.createInheritedMap
让我们继续追踪createInheritedMap:

    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

    /**
     * 构建一个包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
     * 该函数只被 createInheritedMap() 调用.
     */
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        // ThreadLocalMap 使用 Entry[] table 存储ThreadLocal
        table = new Entry[len];

        // 逐一复制 parentMap 的记录
        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) {
                    // 可能会有同学好奇此处为何使用childValue,而不是直接赋值,
                    // 毕竟childValue内部也是直接将e.value返回;
                    // 个人理解,主要为了减轻阅读代码的难度
                    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++;
                }
            }
        }
    }

从ThreadLocalMap可知,子线程将parentMap中的所有记录逐一复制至自身线程

InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,方便必要信息的进一步传递。

public class Test {

    private static List getList(String param) {
        List rst = new ArrayList<>();
        rst.add(param);

        return rst;
    }

    private static final InheritableThreadLocal<List> threadLocal = new InheritableThreadLocal<>();

    public static void test(Consumer<InheritableThreadLocal<List>> consumer) throws InterruptedException {
        threadLocal.set(getList("test"));

        Thread child = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()){
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("子线程中threadLocal的值:" + threadLocal.get());
            }
        });

        System.out.println("主线程中threadLocal的值:" + threadLocal.get());
        child.start();
        TimeUnit.MILLISECONDS.sleep(1);
        consumer.accept(threadLocal);
        System.out.println("主线程中threadLocal的值:" + threadLocal.get());
        TimeUnit.MILLISECONDS.sleep(3);
        child.interrupt();
    }

    public static void main(String[] args) throws InterruptedException {
        //**创建子线程完成后,主线程调用set方法修改值,不会影响到子线程
        test(local -> local.set(getList("test1")));
        System.out.println("===========================");
        //**保存list对象时,通过get方法获取,然后修改list的值,则会影响到子线程
        test(local -> local.get().set(0, "test2"));
    }
}

//**执行结果
主线程中threadLocal的值:[test]
    子线程中threadLocal的值:[test]
    主线程中threadLocal的值:[test1]
    子线程中threadLocal的值:[test]
    ===========================
    主线程中threadLocal的值:[test]
    子线程中threadLocal的值:[test]
    主线程中threadLocal的值:[test2]
    子线程中threadLocal的值:[test2]
3.7 TransmittableThreadLocal详解

用于解决使用线程池缓存线程的组件的情况下传递ThreadLocal
使用场景:分布式跟踪系统,应用容器或上下层框架跨应用代码给下层SDK传递信息,日志收集系统上下文,

源码分析:

TransmittableThreadLocal 继承自 InheritableThreadLocal,这样可以在不破坏ThreadLocal 本身的情况下,使得当用户利用 new Thread() 创建线程时仍然可以达到传递InheritableThreadLocal 的目的。

public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T>{......}

TransmittableThreadLocal相比于InheritableThreadLocal比较关键的一点是引入了holder变量,这样就不必对外暴露Thread中的inherittablethreadlocals变量保存ThreadLocalMap的封装性

// 理解holder,需注意如下几点:
// 1、holder 是 InheritableThreadLocal 变量;
// 2、holder 是 static 变量;
// 3、value 是 WeakHashMap;
// 4、深刻理解 ThreadLocal 工作原理;
private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
    new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
        @Override
        protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
            return new WeakHashMap<>();
        }

        @Override
        protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
            return new WeakHashMap<>(parentValue);
        }
    };
// 调用 get() 方法时,同时将 this 指针放入 holder
public final T get() {
    T value = super.get();
    if (null != value) {
        addValue();
    }
    return value;
}
void addValue() {
    if (!holder.get().containsKey(this)) {
        holder.get().put(this, null); // WeakHashMap supports null value.
    }
}
// 调用 set() 方法时,同时处理 holder 中 this 指针
public final void set(T value) {
    super.set(value);
    if (null == value) { // may set null to remove value
        removeValue();
    } else {
        addValue();
    }
}
void removeValue() {
    holder.get().remove(this);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言内存函数详解

文章目录 前言一、memcpy函数&#xff08;内存拷贝函数&#xff09;二、memmove重叠拷贝函数三.memset内存设置函数四.memcmp内存比较函数总结 前言 我们之前按学习了C语言标准库中提供了一系列的字符和字符串库函数&#xff0c;接下来我们就学习一下关于内存相关的一些函数。…

FPGA和ASIC

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;这是我所总结作为学习的笔记第16篇,在本篇文章给大家介绍FPGA和ASIC。 一个四核i7的CPU的晶体管中有20亿的晶体管&#xff0c;需要链接起20亿的晶体管可不是一件容易的事情&#xff0c;所以设计一个CPU需要用年来算&#x…

Springboot全局异常处理

Springboot全局异常处理 一、不使用全局异常处理器二、全局异常处理器1.自定义常量&#xff08;返回状态码&#xff09;2.手动抛出异常4.编写全局异常处理器3.测试结果 三、全局异常处理方式二1.定义状态码常量2. 定义基础接口&#xff08;面向接口编程&#xff09;3.定义枚举类…

Linux安装vLLM模型推理框架问题总汇

简介 vLLM 是一个专为大规模语言模型&#xff08;Large Language Models, LLM&#xff09;推理优化的服务框架和推理引擎。它可以高效地管理和部署预先训练好的大型语言模型&#xff0c;尤其是那些具有极高参数数量和复杂度的模型&#xff0c;如GPT系列及其他基于Transformer架…

字符分类函数(iscntrl、i是space.....)---c语言

目录 一、定义二、字符分类函数2.1 -iscntrl&#xff08;&#xff09;2.1.1定义2.1.2使用举例 2.2 -isspace&#xff08;&#xff09;2.2.1描述2.2.2使用举例 2.3-isdigit()2.3.1描述2.3.2使用举例 2.4-isxdigit()2.4.1描述 2.5-islower()2.5.1描述2.5.2使用举例 2.6-isupper()…

VXLAN学习笔记

声明&#xff1a;该博客内容大部分参考参考链接整理 什么是VXLAN&#xff1f; VXLAN(Virtual Extensible LAN)即虚拟扩展局域网&#xff0c;是大二层网络中广泛使用的网络虚拟化技术。在源网络设备与目的网络设备之间建立一条逻辑VXLAN隧道&#xff0c;采用MAC in UDP的封装方…

【开源】SpringBoot框架开发房屋出售出租系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 房屋销售模块2.2 房屋出租模块2.3 预定意向模块2.4 交易订单模块 三、系统展示四、核心代码4.1 查询房屋求租单4.2 查询卖家的房屋求购单4.3 出租意向预定4.4 出租单支付4.5 查询买家房屋销售交易单 五、免责说明 一、摘…

MySQL语法分类 DQL(5)分组查询

为了更好的学习这里给出基本表数据用于查询操作 create table student (id int, name varchar(20), age int, sex varchar(5),address varchar(100),math int,english int );insert into student (id,name,age,sex,address,math,english) values (1,马云,55,男,杭州,66,78),…

YOLOV5 模型:利用tensorboard查看网络结构和yaml文件介绍

1、前言 yolov5目录中,关于模型构建的为下面的部分: *.yaml 文件,具体的配置参数common文件,具体模块的搭建,neck的spp等等yolo文件,搭建yolo的代码这部分为了后续添加改进模块,所以详细介绍下 2、yaml 文件 因为之前实战的模型都是v5s,这里我们打开yolov5s.yaml文件…

[Windows] Win11 常用快捷键

文章目录 &#x1f680; [Windows] Win11 常用快捷键&#x1f310; Windows 操作系统&#x1f525; Windows 11 &#x1f310; Windows 11 快捷键概览&#x1f525; 基本快捷键&#x1f525; 窗口快捷键&#x1f525; 功能快捷键 &#x1f4dd; 小结 &#x1f680; [Windows] W…

RPC通信原理(一)

RPC通信原理 RPC的概念 如果现在我有一个电商项目&#xff0c;用户要查询订单&#xff0c;自然而然是通过Service接口来调用订单的实现类。 我们把用户模块和订单模块都放在一起&#xff0c;打包成一个war包&#xff0c;然后再tomcat上运行&#xff0c;tomcat占有一个进程&am…

ResNet学习笔记

一、residual结构 优点&#xff1a; &#xff08;1&#xff09;超深的网络结构(突破1000层) &#xff08;2&#xff09;提出residual模块 &#xff08;3&#xff09;使用Batch Normalization加速训练(丢弃dropout) 解决问题&#xff1a; &#xff08;1&#xff09; 梯度消失和…

高效采购:最大化利用电子元器件采购商城

实现高效采购需要最大化利用电子元器件采购商城的各项功能和资源。以下是一些方法和策略&#xff1a; 充分利用搜索和筛选功能&#xff1a; 采购商城通常提供强大的搜索和筛选功能&#xff0c;包括关键词搜索、参数筛选、品牌筛选等。通过充分利用这些功能&#xff0c;可以快速…

Linux中udp服务端,客户端的开发

UDP通信相关函数&#xff1a; ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); 函数说明&#xff1a;接收信息 参数说明&#xff1a;sockfd:套接字buf:要接收的缓冲区len:缓冲区…

人形机器人进展:IEEE Robotics出版双臂通用协同机械手操作架构

文章目录 1. Main2. My ThoughtsReference彩蛋a. OpenAI 投资: 人形机器人公司 Figure AIb. 人工智能软件工程师 Devin 上线 1. Main 图1 人居环境下的人形双臂机器人系统 通用人形机器人 作为近年来机器人与AI交叉领域的研究热点和技术竞争高地&#xff0c;因其具备在 非结构化…

python二级备考(3)-综合应用

1 《命运》是著名科幻作家倪匡的作品。这里给出《命运》的一个网络版本文件&#xff0c;文件名为“命运. txt”。 问题1 (5分) :在PY301-1. py文件中修改代码&#xff0c;对“命运. txt”文件进行字符频次统计&#xff0c;输出频次最高的中文字符(不包含标点符号)及其频次&…

【C++补充1】map容器

1.单映射 1.单映射&#xff0c;first:键 second:值 2.键唯一&#xff0c;如果重复&#xff0c;相同键插入 会覆盖值。 使用方法&#xff1a;pair<int, string> data(1, "Iloveyou"); 1.main int main() {//单映射//first:键 second:值//键唯一&am…

如何选择适合自己的编程语言?

如何选择适合自己的编程语言&#xff1f; 《探索编程语言&#xff1a;如何选择适合自己的编程语言&#xff1f;》摘要引言如何选择适合自己的编程语言&#xff1f;1. 了解不同的编程范式2. 考虑所需的工作领域3. 考虑生态系统和社区支持4. 考虑学习曲线和语法简洁性 总结参考资…

JVM虚拟机:通过jconsole远程连接解决JVM报错

本文重点 前面我们介绍过的一些工具都是使用命令行的方式来帮助我们完成&#xff0c;本文我们将使用一种图形化界面的方式来远程连接&#xff0c;然后完成关于JVM的检测任务。 jconsole jconsole是一个JVM的检测工具&#xff0c;这个工具任何安装了Java的电脑上都有的&#…

学生时期学习资源同步-1 第一学期结业考试题8

原创作者&#xff1a;田超凡&#xff08;程序员田宝宝&#xff09; 版权所有&#xff0c;引用请注明原作者&#xff0c;严禁复制转载