在Java
多线程编程中,通过ThreadLocal
实现了线程本地变量机制,通过空间换时间达到了线程隔离的目的。
但是,现在有这样一个需求:父线程中的线程本地变量需要传递给子线程使用。
针对这种情况,通过ThreadLocal
是无法完成的,因为ThreadLocal
中保存的是线程的本地变量,父线程的线程本地变量只有自己能拿到,子线程无法拿到。如下代码演示:
package com.tao.springbootdemo.thread;
public class ParentAndChildThreadMain {
public static void main(String[] args) {
// 父线程
Thread parentThread = new Thread(new Runnable() {
// 父线程中的线程局部变量
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
// private InheritableThreadLocal threadLocal = new InheritableThreadLocal<>();
@Override
public void run() {
System.out.println("> 父线程中设置线程的本地变量");
threadLocal.set("local variable");
System.out.println("> 父线程中拿到的线程本地变量是:" + threadLocal.get());
// 在父线程中再起一个子线程
Thread childThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("> 子线程中获取父线程的本地变量");
System.out.println("> 子线程中拿到的父线程本地变量是:" + threadLocal.get());
}
});
childThread.setName("子线程!");
childThread.start();
}
});
parentThread.setName("父线程!");
parentThread.start();
}
}
得到的输出结果是:
> 父线程中设置线程的本地变量
> 父线程中拿到的线程本地变量是:local variable
> 子线程中获取父线程的本地变量
> 子线程中拿到的父线程本地变量是:null
Process finished with exit code 0
那么,怎样解决这个需求呢?
Java
提供了一个InheritableThreadLocal
来解决线程本地变量在父子线程中传递的问题!!!
如下代码演示:
package com.tao.springbootdemo.thread;
public class ParentAndChildThreadMain {
public static void main(String[] args) {
// 父线程
Thread parentThread = new Thread(new Runnable() {
// 父线程中的线程局部变量
// private ThreadLocal threadLocal = new ThreadLocal<>();
private InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
@Override
public void run() {
System.out.println("> 父线程中设置线程的本地变量");
threadLocal.set("local variable");
System.out.println("> 父线程中拿到的线程本地变量是:" + threadLocal.get());
// 在父线程中再起一个子线程
Thread childThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("> 子线程中获取父线程的本地变量");
System.out.println("> 子线程中拿到的父线程本地变量是:" + threadLocal.get());
}
});
childThread.setName("子线程!");
childThread.start();
}
});
parentThread.setName("父线程!");
parentThread.start();
}
}
得到的输出结果为:
> 父线程中设置线程的本地变量
> 父线程中拿到的线程本地变量是:local variable
> 子线程中获取父线程的本地变量
> 子线程中拿到的父线程本地变量是:local variable
Process finished with exit code 0
我们只是将ThreadLocal
改成了InheritableThreadLocal
,子线程中便能取到父线程的线程局部变量了。
首先看下Thread
类:
public class Thread implements Runnable {
......
/**
* 与此线程相关的ThreadLocal值,保存线程本地变量。
* 这个map由ThreadLocal类维护。
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* 与此线程相关的那些从父线程继承而来的ThreadLocal值。
* 这个map由InheritableThreadLocal类维护。
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
可以看出,threadLocals
和inheritableThreadLocals
由两个实例域维护,相互之间互不影响。
然后,再看下InheritableThreadLocal
的源码:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* 该函数在父线程创建子线程,向子线程复制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);
}
}
InheritableThreadLocal
继承自ThreadLocal
;ThreadLocal
的3个函数;getMap()
、createMap()
函数非常重要,操作的是t.inheritableThreadLocals
,这两个函数在调用set(T value)
和get()
函数的时候起到了非常重要的作用;ThreadLocal
互不影响。首先,从父线程创建子线程开始。
父线程调用new Thread()
创建子线程:
Thread childThread = new Thread();
调用了Thread
类的构造函数:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
// 默认情况下,设置inheritThreadLocals为可传递
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
传递给子线程。如果父线程中使用的是InheritableThreadLocal
保存线程本地变量,那么在调用set(T value)
的时候,操作的就是线程的inheritableThreadLocals
,所以parent.inheritableThreadLocals != null
。
其中,通过调用ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)
来将父线程的inheritableThreadLocals
传入并创建子线程的inheritableThreadLocals
。
查看ThreadLocal.createInheritedMap
函数的源码:
// 创建线程的inheritableThreadLocals
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* 接收传进来的 parent.inheritableThreadLocals,
* 构建一个包含parent.inheritableThreadLocals中所有键值对的ThreadLocalMap,
* 该函数只被 createInheritedMap() 调用.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
// 逐个复制parent.inheritableThreadLocals中的Entry
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
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
// 保存
table[h] = c;
size++;
}
}
}
}
从上边的源码分析可以看出,在创建子线程的时候,将父线程中inheritableThreadLocals
对应的ThreadLocalMap
中的所有的Entry
全部复制到一个新的ThreadLocalMap
中,最后将这个ThreadLocalMap
赋值给了子线程的inheritableThreadLocals
。
当在子线程中调用父线程的InheritableThreadLocal
的threadLocal.get()
方法,获取父线程的线程本地变量的时候,查看源码:
public T get() {
Thread t = Thread.currentThread();
// InheritableThreadLocal重写了getMap,返回的是线程的inheritableThreadLocals
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();
}
可以看出,
InheritableThreadLocal
类中重写了getMap()
,返回的是线程的inheritableThreadLocals
。get()
拿到的是当前线程中inheritableThreadLocals
这个ThreadLocalMap
。get()
返回的值就是保存在子线程的inheritableThreadLocals
中的值。编写代码测试:
package com.tao.springbootdemo.thread;
public class ParentAndChildThreadMain {
public static void main(String[] args) {
// 父线程
Thread parentThread = new Thread(new Runnable() {
// 父线程中的线程局部变量
// private ThreadLocal threadLocal = new ThreadLocal<>();
private InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
@Override
public void run() {
System.out.println("> 父线程中设置线程的本地变量");
threadLocal.set("local variable");
System.out.println("> 父线程中拿到的线程本地变量是:" + threadLocal.get());
// 在父线程中再起一个子线程
Thread childThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("> 子线程中获取父线程的本地变量");
System.out.println("> 子线程中拿到的父线程本地变量是:" + threadLocal.get());
try {
System.out.println("> 子线程 sleep 5秒");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("> 子线程中再次获取父线程的本地变量");
System.out.println("> 子线程中拿到的父线程本地变量是:" + threadLocal.get());
}
});
childThread.setName("子线程!");
childThread.start();
try {
System.out.println("> 父线程 sleep 2秒");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("> 父线程中再次设置线程的本地变量");
threadLocal.set("AAA");
System.out.println("> 父线程中拿到的改变后的线程本地变量是:" + threadLocal.get());
}
});
parentThread.setName("父线程!");
parentThread.start();
}
}
程序输出结果:
> 父线程中设置线程的本地变量
> 父线程中拿到的线程本地变量是:local variable
> 父线程 sleep 2秒
> 子线程中获取父线程的本地变量
> 子线程中拿到的父线程本地变量是:local variable
> 子线程 sleep 5秒
> 父线程中再次设置线程的本地变量
> 父线程中拿到的改变后的线程本地变量是:AAA
> 子线程中再次获取父线程的本地变量
> 子线程中拿到的父线程本地变量是:local variable
Process finished with exit code 0
可以看到,子线程不会更新从父线程传递过来的线程本地变量!!!
InheritableThreadLocal
主要用于在创建子线程的时候,需要自动继承父线程的线程本地变量以便使用。