InheritableThreadLocal用于子线程继承父线程的数值。将通过重写initialValue() 与childValue(Object parentValue)两个方法来展示例子。
其中initialValue()是InheritableThreadLocal类继承于ThreadLocal类的,用于初始化当前线程私有初始值,childValue(Object parentValue)是InheritableThreadLocal类的,作用是继承父线程的初始值并且进一步处理。
使用示例:
public class test {
/**
* 静态调用IhThreadLocal
*/
private static test.IThreadLocal iThreadLocal = new test.IThreadLocal();
/**
* 父线程输出IhThreadLocalUse的值与分两次创建子线程查看内IhThreadLocalUse的值
*
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// 情况1:在父类初始化IhThreadLocal前子线程先执行子线程
test.Ithread iThread1 = new test.Ithread();
iThread1.start();
Thread.sleep(1000);
// 情况2:在父线程初始化IhThreadLocal后执行子线程
for (int i = 0; i < 2; i++) {
System.out.println(iThreadLocal.get());
}
test.Ithread iThread2 = new test.Ithread();
iThread2.start();
}
/**
* 继承InheritableThreadLocal方法,并且重写初始化线程数值方法与修改子线程数值方法
*/
static class IThreadLocal extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return LocalDateTime.now();
}
@Override
protected Object childValue(Object parentValue) {
return "子线程获取父线程传递数据:" + parentValue;
}
}
/**
* 输出子线程的IhThreadLocalUse的值
*/
static class Ithread extends Thread {
@SneakyThrows
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println(iThreadLocal.get());
Thread.sleep(100);
}
}
}
}
输出结果:
2022-09-18T12:42:47.361
2022-09-18T12:42:47.361
2022-09-18T12:42:48.312
2022-09-18T12:42:48.312
子线程获取父线程传递数据:2022-09-18T12:42:48.312
子线程获取父线程传递数据:2022-09-18T12:42:48.312
结论:
为什么InheritableThreadLocal 的子线程可以继承父线程值?
InheritableThreadLocal的源代码:
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,并且重写了getMap和createMap方法,区别就是将 ThreadLocal 中的 threadLocals 换成了 inheritableThreadLocals,这两个变量都是ThreadLocalMap类型,并且都是Thread类的属性。
下面就一步步来看下InheritableThreadLocal为什么能拿到父线程中的ThreadLocal值。
step1:InheritableThreadLocal获取值先调用了ThreadLocal的get方法,所以我们直接看看get方法都做了些啥。
public T get() {
Thread t = Thread.currentThread();
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();
}
从上面的代码可以看出,get方法和ThreadLocal中是一样的,唯一有区别的就是其中的getMap方法重写了,返回的是inheritableThreadLocals属性。这个属性也是一个ThreadLocalMap类型的变量。那么从这边就可以推断出来:肯定是在某处将父线程中的ThreadLocal值赋值到了子线程的inheritableThreadLocals中。
step2:在源代码中搜索哪些地方使用到了inheritableThreadLocals这个属性,最后找到Thread的init()代码
/**
* Initializes a Thread.
*
* @param g 线程组
* @param target run() 方法被调用的对象
* @param name 新线程的名称
* @param stackSize 新线程所需的堆栈大小,或为零表示要忽略此参数。
* @param acc 要继承的 AccessControlContext,如果为 null,则为 AccessController.getContext()
* @param inheritThreadLocals 如果 {@code true},从构造线程继承可继承线程局部变量的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
// 第一项inheritThreadLocals 是传进来的boolean值,重载时传的是true,
// 第二项就是判断父线程中的inheritableThreadLocals属性是否为空,不为空的话
// 两个条件同时满足,把父线程的inheritableThreadLocals复制给子线程
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
// 则新创建出来的子线程的inheritableThreadLocals 变量就和父线程的inheritableThreadLocals 的内容一样了。
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
}
InheritableThreadLocal和线程池搭配使用时,可能得不到想要的结果,因为线程池中的线程是复用的,并没有重新初始化线程,InheritableThreadLocal之所以起作用是因为在Thread类中最终会调用init()方法去把InheritableThreadLocal的map复制到子线程中。由于线程池复用了已有线程,所以没有调用init()方法这个过程,也就不能将父线程中的InheritableThreadLocal值传给子线程。
public class test {
private static ExecutorService executorService = Executors.newFixedThreadPool(1);
private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 主线程第一次赋值
inheritableThreadLocal.set("first set1");
System.out.println("父线程第一次获取值:" + inheritableThreadLocal.get());
executorService.submit(new Ithread());
Thread.sleep(2000);
inheritableThreadLocal.set("second set2");
System.out.println("父线程第二次获取值:" + inheritableThreadLocal.get());
executorService.submit(new Ithread());
executorService.shutdown();
}
/**
* 输出子线程的IhThreadLocalUse的值
*/
static class Ithread extends Thread {
@SneakyThrows
@Override
public void run() {
System.out.println("子线程值:" + inheritableThreadLocal.get());
}
}
}
结果:
父线程第一次获取值:first set1
子线程值:first set1
父线程第二次获取值:second set2
子线程值:first set1
可以看出,我们在父线程中第二次set并没有被第二次submit的线程get到。