IheritableThreadLocal解决子线程的变量传递问题

大家好,上一篇,我们介绍了 ThreadLocal都有哪些需要掌握的知识 ,但当线程需要把变量传递给子线程时,子线程无法获取父线程的本地变量。今天我们一起聊聊线程的另一个工具类–InheritableThreadLocal,它可以把本地变量传递给子线程。

大纲

IheritableThreadLocal解决子线程的变量传递问题

变量传递给子线程

我们来看看,采用ThreadLocal和InheritableThreadLocal在传递变量给子线程中的效果。

采用ThreadLocal方式

代码

public class ThreadLocalTest {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    private static final ThreadLocal<String> threadLocalInheritable = new InheritableThreadLocal<>();

    /**
     * 主线程中设置的ThreadLocal变量,在子线程中无法获取
     * **/

    @Test
    public void threadLocalTest03(){
        threadLocal.set("ThreadLocalTest");
        Thread t = new Thread(()->{
            System.out.println("子线程获取值:"+threadLocal.get());
        });
        t.start();
        System.out.println("主线程获取值:"+threadLocal.get());
    }
}

代码很简单,在父线程中设置threadLocal变量的值为ThreadLocalTest,父线程开启一个子线程,在子线程和父线程中分别打印父线程threadLocal的变量值。

运行结果

主线程获取值:ThreadLocalTest
子线程获取值:null

进程已结束,退出代码 0

结论

通过测试结果,我们发现,在父线程中设置的threadLocal变量值,在子线程中,拿到的是null,即ThreadLocal的变量值,不具备线程间的传递性。

采用InheritableThreadLocal方式

代码

/**
 * 通过InheritableThreadLocal来设置变量,可以在子线程中获取对应的值
 * **/

@Test
public void threadLocalTest04(){
    threadLocalInheritable.set("ThreadLocalTest");
    Thread t = new Thread(()->{
        System.out.println("子线程获取变量值:"+threadLocalInheritable.get());
        Thread tt = new Thread(()->{
            System.out.println("孙线程获取变量值:"+threadLocalInheritable.get());
        });
        tt.start();
    });
    t.start();
    System.out.println("主线程获取变量值:"+threadLocalInheritable.get());
}

代码实现的功能,同ThreadLocal,在父线程中设置threadLocalInheritable变量的值,父线程创建子线程,并在子线程中获取threadLocalInheritable变量的值,然后由子线程再创建一个孙线程,再在孙线程中,获取threadLocalInheritable变量的值。

运行结果

主线程获取变量值:ThreadLocalTest
子线程获取变量值:ThreadLocalTest
孙线程获取变量值:ThreadLocalTest

进程已结束,退出代码 0

结论

通过以上验证,我们发现,ThreadLocal的派生类InheritableThreadLocal,是可以传递变量的值给子线程的,InheritableThreadLocal对于子线程来说具有线程间的传递性。

InheritableThreadLocal概念

InheritableThreadLocal是Java中的一个类,它继承了ThreadLocal类,主要目的是为了在子线程中能够访问父线程中设置的ThreadLocal变量的值。

InheritableThreadLocal原理

I nheritableThreadLocal扩展了ThreadLocal的功能,以提供从父线程到子线程的值的继承。当创建子线程时,子线程会接收父线程具有的所有可继承线程局部变量的初始值。

InheritableThreadLocal实现了一个childValue()方法,该方法在子线程中调用,将父线程中的变量值传递给子线程。默认情况下,子线程会获得与父线程相同的值,但你可以通过重写childValue()方法来改变这一行为。

InheritableThreadLocal作用

通过重写initialValue()和childValue(Object parentValue)两个方法来使用InheritableThreadLocal。initialValue()方法用于提供线程局部变量的初始值,而childValue(Object parentValue)方法则用于确定子线程应如何继承父线程的值。

InheritableThreadLocal存在的问题

资源消耗

InheritableThreadLocal相对于ThreadLocal会消耗更多的资源。这是因为InheritableThreadLocal需要在每个新创建的子线程中维护一个引用,以便在需要时将父线程中的变量传递给子线程。

与线程池搭配使用的问题

当与线程池搭配使用时,InheritableThreadLocal可能会出现问题。因为线程池中的线程是复用的,而不是为每个任务都创建新的线程,所以子线程可能无法正确地继承父线程中的InheritableThreadLocal变量的值。

内存泄漏

与ThreadLocal一样,InheritableThreadLocal也可能会导致内存泄漏。当一个线程结束时,它使用的所有InheritableThreadLocal实例通常不会被垃圾收集器回收,除非显式地删除这些实例。因此,在使用完InheritableThreadLocal后,最好及时清理相关资源。

InheritableThreadLocal源码解读

Thread类

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */

    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */

    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    public Thread() {
        init(nullnull"Thread-" + nextThreadNum(), 0);
    }

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    Thread(Runnable target, AccessControlContext acc) {
        init(null, target, "Thread-" + nextThreadNum(), 0, acc);
    }

    public Thread(ThreadGroup group, Runnable target) {
        init(group, target, "Thread-" + nextThreadNum(), 0);
    }

    public Thread(String name) {
        init(nullnull, name, 0);
    }

    public Thread(ThreadGroup group, String name) {
        init(group, null, name, 0);
    }

    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }

    public Thread(ThreadGroup group, Runnable target, String name) {
        init(group, target, name, 0);
    }

    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize)
 
{
        init(group, target, name, stackSize);
    }
}

Thread类有两个成员变量ThreadLocal.ThreadLocalMap threadLocals 和 ThreadLocal.ThreadLocalMap inheritableThreadLocals,构造函数,均调用了init方法,那么我们再看看init方法是否何方大神。

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc)
 
{
    /** 此处省略无关代码 **/

    Thread parent = currentThread();
   
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

如果父线程的inheritableThreadLocals不为空,则把父线程的inheritableThreadLocals,传递给了新创建线程的inheritableThreadLocals变量。这里用到了ThreadLocal.createInheritedMap,我们看看ThreadLocal的createInheritedMap方法。

ThreadLocal.ThreadLocalMap类

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

通过父线程的ThreadLocalMap,来创建子线程的ThreadLocalMap。我们再看看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++;
            }
        }
    }
}

在ThreadLocalMap的构造函数中,调用了InheritableThreadLocal类重写的childValue()方法。因  InheritableThreadLocal类重写了父类ThreadLocal的getMap和createMap方法,本地变量保存到了线程对象的inheritableThreadLocals属性中,InheritableThreadLocal类通过set()和get()方法设置变量时,就会创建当前线程的inheritableThreadLocals属性。故,当父线程创建子线程时,会把父线程中的inheritableThreadLocals变量复制一份到子线程的inheritableThreadLocals变量中,从而实现了变量从父线程到子线程的传递。

为了更直观,我们分别看看ThreadLocal类和其子类InheritableThreadLocal的getMaphe createMap方法,方便理解。

以下是ThreadLocal类的getMap和createMap方法的代码。

public class ThreadLocal<T{
    /** 此处省略其它非关联代码 **/
   
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

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

InheritableThreadLocal类

下面是InheritableThreadLocal类重写父类ThreadLoca的getMap和createMap方法的源码。

public class InheritableThreadLocal<Textends ThreadLocal<T{
    
    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

总结

ThreadLocal可以方便的保存线程的变量,避免重复创建,以提升系统性能,但其不具备变量的传递性。InheritableThreadLocal类为ThreadLocal的派生类,通过重写getMap和createMap方法,将父线程的inheritableThreadLocals变量传递给其创建的子线程,从而使子线程可以获取父线程中保存的本地变量。然而,我们现在现实场景中,很少会通过直接创建线程的方式来使用线程,通常会通过线程池的方式使用线程,线程池中的线程,不是父线程创建的,那么在线程池的场景下,实现本地变量的传递呢,我们后续讨论。

原文始发于微信公众号(扬哥手记):IheritableThreadLocal解决子线程的变量传递问题

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/289924.html

(0)
葫芦侠五楼的头像葫芦侠五楼

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!