Java-并发编程--ThreadLocal、InheritableThreadLocal

news2024/11/18 4:25:37

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

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

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

相关文章

深度强化学习(六)(改进价值学习)

深度强化学习(六)(改进价值学习) 一.经验回放 把智能体与环境交互的记录(即经验)储存到 一个数组里&#xff0c;事后反复利用这些经验训练智能体。这个数组被称为经验回放数组&#xff08;replay buffer&#xff09;。 具体来说, 把智能体的轨迹划分成 ( s t , a t , r t ,…

C#,深度好文,精致好码,文本对比(Text Compare)算法与源代码

Vladimir I. Levenshtein 一、文本对比的列文斯坦距离(编辑距离)算法 在日常应用中,文本比较是一个比较常见的问题。文本比较算法也是一个老生常谈的话题。 文本比较的核心就是比较两个给定的文本(可以是字节流等)之间的差异。目前,主流的比较文本之间的差异主要有两大类…

力扣112、113、101--树

112. 路径总和 题目描述&#xff1a; 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。 判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。 如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c…

海豚调度系列之:任务类型——SPARK节点

海豚调度系列之&#xff1a;任务类型——SPARK节点 一、SPARK节点二、创建任务三、任务参数四、任务样例1.spark submit2.spark sql 五、注意事项&#xff1a; 一、SPARK节点 Spark 任务类型用于执行 Spark 应用。对于 Spark 节点&#xff0c;worker 支持两个不同类型的 spark…

TinTin Web3 动态精选:以太坊坎昆升级利好 Layer2,比特币减半进入倒计时

TinTin 快讯由 TinTinLand 开发者技术社区打造&#xff0c;旨在为开发者提供最新的 Web3 新闻、市场时讯和技术更新。TinTin 快讯将以周为单位&#xff0c; 汇集当周内的行业热点并以快讯的形式排列成文。掌握一手的技术资讯和市场动态&#xff0c;将有助于 TinTinLand 社区的开…

【SpringBoot3】整合Druid数据源和Mybatis 项目打包和运行

文章目录 一、整合Druid数据源二、整合Mybatis2.1 MyBatis整合步骤2.1 Mybatis整合实践2.1 声明式事务整合配置2.1 AOP整合配置 三、项目打包和运行命令启动和参数说明 总结web 与 springboot 打包区别JDK8的编译环境 执行17高版本jar 一、整合Druid数据源 创建模块 &#xff1…

2024年【天津市安全员C证】考试资料及天津市安全员C证考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 天津市安全员C证考试资料根据新天津市安全员C证考试大纲要求&#xff0c;安全生产模拟考试一点通将天津市安全员C证模拟考试试题进行汇编&#xff0c;组成一套天津市安全员C证全真模拟考试试题&#xff0c;学员可通过…

微信小程序原生<map>地图实现标记多个位置以及map 组件 callout 自定义气泡

老规矩先上效果图: 1 、在pages文件夹下新建image文件夹用来存放标记的图片。 2、代码片段 也可以参考小程序文档:https://developers.weixin.qq.com/miniprogram/dev/component/map.html index.wxml代码 <mapid="map"style="width: 100%; height:100%;&…

2024批量导出公众号所有文章生成目录,这下方便找文章了

公众号历史文章太多&#xff0c;手机上翻起来太费劲&#xff0c;怎么快速找到某一天的文章呢&#xff1f;比如深圳卫健委这个号从2014到2024发布近万篇文章。 公众号历史文章太多&#xff0c;手机上翻起来太费劲&#xff0c;怎么快速找到某一天的文章&#xff1f; 如果要找2020…

【中等】保研/考研408机试-二叉树相关

目录 一、基本二叉树 1.1结构 1.2前序遍历&#xff08;注意三种遍历中Visit所在的位置&#xff09; 1.2中序遍历 1.3后序遍历 二、真题实战 2.1KY11 二叉树遍历&#xff08;清华大学复试上机题&#xff09;【较难】 2.2KY212 二叉树遍历二叉树遍历&#xff08;华中科技大…

印度交易所股票行情数据API接口

1. 历史日线 # Restful API https://tsanghi.com/api/fin/stock/XNSE/daily?token{token}&ticker{ticker}默认返回全部历史数据&#xff0c;也可以使用参数start_date和end_date选择特定时间段。 更新时间&#xff1a;收盘后3~4小时。 更新周期&#xff1a;每天。 请求方式…

基于SSM SpringBoot vue办公自动化计划管理系统

基于SSM SpringBoot vue办公自动化计划管理系统 系统功能 登录注册 个人中心 员工信息管理 部门信息管理 会议管理 计划管理 行程安排管理 行程进度管理 管理员管理 开发环境和技术 开发语言&#xff1a;Java 使用框架: SSM(Spring SpringMVC Mybaits)或SpringBoot 前端…

数字万用表 (Digital Multimeter)

数字万用表 [Digital Multimeter] 1. Product parameters2. 交流频率测量3. 面板介绍4. 背光屏References 1. Product parameters 2. 交流频率测量 在交流 750V 档处按 HOLD 键切换到市电频率 3. 面板介绍 4. 背光屏 ​ References [1] Yongqiang Cheng, https://yongqiang…

【打工日常】使用Docker部署团队协作文档工具

一、ShowDoc介绍 ​ShowDoc是一个适合IT团队共同协作API文档、技术文档的工具。通过showdoc&#xff0c;可以方便地使用markdown语法来书写出API文档、数据字典文档、技术文档、在线excel文档等等。 响应式网页设计&#xff1a;可将项目文档分享到电脑或移动设备查看。同时也可…

easyExcel 导入、导出Excel 封装公共的方法

文档包含三部分功能 1、easyExcel 公共导出list<对象>方法&#xff0c;可以自定义excel中第一行和样式 2、easyExcel 导入逻辑&#xff0c;结合spring Validator 验证导入数据是否符合规范 3、easyExcel 自定义导出 list<map> 、 list<对象> &#xff08;可…

【论文阅读】IRNet:具有像素间关系的实例分割的弱监督学习

【论文阅读】IRNet:具有像素间关系的实例分割的弱监督学习 文章目录 【论文阅读】IRNet:具有像素间关系的实例分割的弱监督学习一、介绍二、联系工作三、方法四、实验结果 Weakly Supervised Learning of Instance Segmentation with Inter-pixel Relations 本文提出了一种以图…

2024043期传足14场胜负前瞻

2024043期售止时间为3月17日&#xff08;周日&#xff09;21点30分&#xff0c;敬请留意&#xff1a; 本期深盘多&#xff0c;1.5以下赔率1场&#xff0c;1.5-2.0赔率7场&#xff0c;其他场次是平半盘、平盘。本期14场整体难度中等偏上。以下为基础盘前瞻&#xff0c;大家可根据…

Java后端面试经验分享,~纯分享

本文将从面试、工作、学习三个方面分享最近面试的一些心得以及以后发展的一些规划&#xff0c;仅供参考&#xff0c;哈哈&#xff0c;毕竟本人也很菜&#xff0c;因为菜才要多学习。一会儿也会分享两本Java面试题库&#xff08;题库是b站大学找的&#xff0c;一会儿我也会分享出…

[Vue]组件间通讯

Vue组件间通讯 父子间通讯 非父子间通讯 父子间通讯 父组件通过 props 将数据传递给子组件父向子传值步骤 给子组件以添加属性的方式传值 子组件内部通过props接收 模板中直接使用 props接收 子组件利用 $emit 通知父组件修改更新 $emit触发事件&#xff0c;给父组件…

leetcode代码记录(组合

目录 1. 题目&#xff1a;2. 我的代码&#xff1a;小结&#xff1a; 1. 题目&#xff1a; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [ […