一、引言
众所周知,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的弊端
登录 | 立即注册