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

变量传递给子线程
我们来看看,采用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(null, null, "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(null, null, 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<T> extends 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