最近项目中为了让打印的日志可以追踪,看到了Logback和log4j2支持的MDC功能,其内部利用了子线程从父线程继承InheritableThreadLocal类型变量的特性。以前只使用到了ThreadLocal,在这里做一下对比,并简单的做了一些分析
ThreadLocal与InheritableThreadLocal区别
ThreadLocal声明的变量是线程私有的成员变量,每个线程都有该变量的副本,线程对变量的修改对其他线程不可见
InheritableThreadLocal声明的变量同样是线程私有的,但是子线程可以使用同样的InheritableThreadLocal类型变量从父线程继承InheritableThreadLocal声明的变量,父线程无法拿到其子线程的。即使可以继承,但是子线程对变量的修改对父线程也是不可见的。
原理
ThreadLocal原理:

Thread有一个类型为ThreadLocal.ThreadLocalMap变量:threadLocals,ThreadLocalMap底层是一个Entry数组,用于保存ThreadLocal引用和真正变量(程序中销售人的主管姓名)的值
看下面的代码和注释了解如何将值放入threadLocals的
调用ThreadLocal对象的set()方法
public void set(T value) {
//获取到当前线程对象
Thread t = Thread.currentThread();
//从当前线程对象中拿threadLocals变量
ThreadLocalMap map = getMap(t);
//如果threadLocals不为空,则调用它的set方法,将key=当前ThreadLocal对象 value=主管姓名
if (map != null)
map.set(this, value);
//如果threadLocals为空,则新创建一个ThreadLocalMap
else
createMap(t, value);
}
InheritableThreadLocal原理:

InheritableThreadLocal继承自ThreadLocal,因此可以想到它自己要么重写,要么新增了部分方法。从源码看只是重写了childValue、getMap、createMap方法,其中getMap和createMap都是父类set方法中使用到的,因此主要是改变了父类set方法的行为。
getMap获取的是InheritableThreadLocal类型的变量
createMap创建的是InheritableThreadLocal类型的实例
但是从这点上看是不能达到子线程继承父线程的变量的。再看看Thread的init方法中比以前新增了如下代码:
//如果当前线程的InheritableThreadLocal类型变量inheritThreadLocals为空且父线程的 //inheritThreadLocals不为空,则复制父线程的inheritThreadLocals中的内容给当前线程的 //inheritThreadLocals,是值复制不是引用赋值 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
上面说到重写了三个方法,其中的childValue还没有找到使用的地方,现在该它出场了,就是在继承变量的时候,创建新的ThreadLocalMap使用到了。
createInheritedMap->ThreadLocalMap->{
//创建一个新的Entry数组
table = new Entry[len];
//复制数据
Object value = key.childValue(e.value); Entry c = new Entry(key, value); table[h] = c;
}
测试代码:
ThreadLocal测试
写一个销售人类,该类有一个主管人员成员变量(ThreadLocal声明),将销售人对象传给不同的工作线程,在工作线程中打印销售人的主管,其结果应该是为NULL,因为主管是声明为线程本地变量,是不能共享的
package com.jv.jdk.thread.threadlocal;
import lombok.Data;
@Data
public class Salesman {
private ThreadLocal<String> charger = new ThreadLocal<>();
public Salesman(String charger){
this.charger.set(charger);
}
}
package com.jv.jdk.thread.threadlocal;
import lombok.extern.log4j.Log4j2; //@Log4j2注解可以很方便做日志输出
import java.util.concurrent.CountDownLatch;
@Log4j2
public class Worker implements Runnable{
private Salesman salesman = null;
private CountDownLatch countDownLatch = null;
public Worker(Salesman salesman, CountDownLatch countDownLatch){
this.salesman = salesman;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
log.info(Thread.currentThread().getName()+":"+salesman.getCharger().get());
countDownLatch.countDown();
}
}
测试类
@Test
public void threadLocal(){
CountDownLatch countDownLatch = new CountDownLatch(2);
Salesman salesman = new Salesman("刘墙东");
Thread thread_1 = new Thread(new Worker(salesman,countDownLatch));
Thread thread_2 = new Thread(new Worker(salesman,countDownLatch));
thread_1.start();
thread_2.start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
测试结果:
2018.08.30 at 14:47:45 CST 14-Thread-1 INFO com.jv.jdk.thread.threadlocal.Worker run() @20 - Thread-1:null 2018.08.30 at 14:47:45 CST 15-Thread-2 INFO com.jv.jdk.thread.threadlocal.Worker run() @20 - Thread-2:null
InheritableThreadLocal测试
与上面的代码重写SalesMan类
package com.jv.jdk.thread.threadlocal;
import lombok.Data;
@Data
public class SalesmanInherit extends Salesman{
private InheritableThreadLocal<String> charger = new InheritableThreadLocal<>();
public SalesmanInherit(String charger){
super(charger);
this.charger.set(charger);
}
}
@Test
public void inheritablThreadLocal(){
CountDownLatch countDownLatch = new CountDownLatch(2);
Salesman salesman = new Salesman("刘墙东");
SalesmanInherit salesmanInherit = new SalesmanInherit("刘墙东");
Thread thread_1 = new Thread(new Worker(salesmanInherit,countDownLatch));
Thread thread_2 = new Thread(new Worker(salesmanInherit,countDownLatch));
thread_1.start();
thread_2.start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
测试结果:
2018.08.30 at 14:51:28 CST 15-Thread-2 INFO com.jv.jdk.thread.threadlocal.Worker run() @20 - Thread-2:刘墙东 2018.08.30 at 14:51:28 CST 14-Thread-1 INFO com.jv.jdk.thread.threadlocal.Worker run() @20 - Thread-1:刘墙东
来源:oschina
链接:https://my.oschina.net/u/3049601/blog/1940723