一、引言
众所周知,ThreadLocal是java开发中不可或缺的重要类,而且它在多线程环境下能发挥举足轻重的作用。它的存储结构类似于Map<Thread,Object>,就是以当前线程作为Key的一个Map.所以不同线程之间能做到资源独立,不存在并发访问的问题。由于市面上很多讲ThreadLocal源码的文章,我这里就不再赘述了,我相信绝大部分的童鞋随便花点时间就可以理解。
二、ThreadLocal的使用场景
- spring的事务传播特性
将事务沿着当前线程进行传递. - 稀有资源控制访问
比如数据库连接、分页等等都可以使用ThreadLocal来做。 - 分布式跟踪系统
需要检测线程经过的所有链路的耗时情况。
三、ThreadLocal的弊端
- 跨线程的问题。
由于ThreadLocal具有线程独立副本,线程之间互不干扰。所以它必然就处理不了跨线程的问题。现在考虑这么一个需求,我们先看一个spring 服务代码示例:
public Integer doHandle(){
Integer resultA = methodA();
Integer resultB = longTimeMethod();//耗时服务
Integer resultC = methodC();
Integer resultAll = mergeResult(resultA,resultB,resultC);//合并结果集
return resultAll;
}
已知:
doHandle()是一个事务方法。
需求:
longTimeMethod()方法是一个耗时方法,所以要考虑把它作成异步服务.例如:
new Thread(new Callable<Integer>(){
public Integer call(){
return longTimeMethod();
}
}).start();
但是这样做就会出现一个问题:
因为spring的事务是利用ThreadLocal来做的,如果在事务方法中单独再起一个线程去运行另外一个服务,则另外一个服务就获取不到原来的事务,所以只能把longTimeMethod()单独作成一个事务服务来运行.
四、谈谈InheritableThreadLocal
我们前面已经讲到ThreadLocal有跨进程的问题,也就是线程A的线程独立副本对于新创建的线程B不可见。但是为了把线程A的独立副本传递给子线程来实现更复杂的需求,JDK就实现了InheritableThreadLocal。InheritableThreadLocal继承自ThreadLocal,覆盖了ThreadLocal的getMap和createMap方法。它又是怎么实现线程之间数据的传递的呢?下面我们来说说。
- 先来看下Thread类中的成员变量
/* 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;
竟然Thread还有另外一个和threadLocals类型一样的变量,是不是很惊讶?竟然都没有关注过。那inheritableThreadLocals是在哪里被赋值的呢?我们来看看

原来在new Thread()的时候,会将当前线程的inheritableThreadLocals传递给新创建线程的inheritableThreadLocals变量,即完成了一次"华丽的拷贝"。
- 再来看看InheritableThreadLocal中的几个方法
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);
}
所以我们在子线程中调用get()方法的时候,会从子线程中的inheritableThreadLocals取值。而inheritableThreadLocals就是父线程在new Thread()的时候传递给子线程,一切都恍然大悟!!!难怪子线程可以获取到父线程设置的属性值。下面看一个简单实例来证实一下:
final InheritableThreadLocal<SpanId> inheritableThreadLocal = new InheritableThreadLocal<SpanId>();
inheritableThreadLocal.set(new SpanId("main thread"));
System.out.println(inheritableThreadLocal.get().name);
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("-----------");
System.out.println("--->: " + inheritableThreadLocal.get().name);//父线程将属性值传递到子线程了
inheritableThreadLocal.set(new SpanId("inner thread"));
System.out.println(inheritableThreadLocal.get().name);
System.out.println();
}
};
ExecutorService executorService = Executors.newFixedThreadPool(10);//手动修改线程池大小,这里设置为10,保证是新创建的线程,而不是直接取池中的线程。
executorService.submit(runnable);
TimeUnit.SECONDS.sleep(1);
System.out.println("after sleep-> " + inheritableThreadLocal.get().name);
executorService.submit(runnable); //重新执行任务,又将main线程的inheritableThreadLocals属性值复制到子线程中。
TimeUnit.SECONDS.sleep(1);
System.out.println("########");
SpanId span = inheritableThreadLocal.get();
System.out.println("main-> " + span.name);
输出结果:
main thread
-----------
--->: main thread
inner thread
after sleep-> main thread
-----------
--->: main thread //这里依旧是main thread,因为是 new Thread(),所以将值传递进来了
inner thread
########
main-> main thread
将newFixedThreadPool中的线程数改成1再试一次。执行结果如下:
main thread
-----------
--->: main thread
inner thread
after sleep-> main thread
-----------
--->: inner thread//由于线程数固定为1,所以第二次执行任务时直接取池中的线程,不会实现独立数据副本的拷贝,故不能传递父线程的数据。只能打印原来线程的数据。
inner thread
########
main-> main thread
五、InheritableThreadLocal的弊端
InheritableThreadLocal的弊端也很明显,就是不能实现池化父子线程之间数据的传递。如果我们现在要做一个分布式链路监控系统,想依赖线程独立副本变量来做,就必须要攻克这个难关,因为在线程池大行其道的时代,随便一个框架都用了各种线程池。幸运的是,今天偶然发现阿里提供了这个问题的解决方案,那就是transmittable-thread-local.想深入研究的朋友,可以去github看它的源码。比较晚了,这里就不展开了,下次在细讲transmittable-thread-local。
参考文档:
https://github.com/alibaba/transmittable-thread-local
https://t.hao0.me/devops/2016/10/15/distributed-invoke-trace.html
https://www.tuicool.com/articles/f2qAZnZ
http://blog.csdn.net/a837199685/article/details/52712547
网友评论