欢迎关注公众号【sharedCode】致力于主流中间件的源码分析, 我的网站:https://www.shared-code.com/java
前言
在实际的工做中,有些时候咱们会遇到一些线程之间共享数据的问题,好比一下几个典型的业务场景,举几个例子,你们可以更加直观的感觉到异步
-
分布式链路追踪系统分布式
这个没必要多说,若是咱们须要记录系统的每一个调用链路,那么每个子系统里面,若是调用了异步线程来作处理的话,那么相似这种链路是否是须要收集起来呢?ide
-
日志收集记录系统上下文工具
在实际的日志打印记录中,一个http请求进来的话,每一行日志,日志产生的线程信息?上下文信息,是否是须要记录下来呢?源码分析
上面我举的是咱们最最最多见的两个例子了, 作了几年开发的都能理解为啥要有这个东西,下面咱们仔细聊一下这个问题,网站
InheritableThreadLocal
其实说到这个问题,有些同窗就会想到InheritableThreadLocal
这个工具了,这是JDK给咱们提供的的工具,该工具能够解决父子线程之间的值的传递,咱们先来一个简单的demo, 而后再进行原理分析this
demo
/** * ce * * @author zhangyunhe * @date 2020-04-22 16:19 */ public class InteritableTest2 { static ThreadLocal<String> local = new InheritableThreadLocal<>(); // 初始化一个长度为1 的线程池 static ExecutorService poolExecutor = Executors.newFixedThreadPool(1); public static void main(String[] args) throws ExecutionException, InterruptedException { InteritableTest2 test = new InteritableTest2(); test.test(); } private void test(){ // 设置一个初始值 local.set("天王老子"); poolExecutor.submit(new Task()); } class Task implements Runnable{ @Override public void run() { // 子线程里面打印获取到的值 System.out.println(Thread.currentThread().getName()+":"+local.get()); } } }
输出结果线程
pool-1-thread-1:天王老子
从上面能够看到, 子线程pool-1-thread-1能够获取到父线程在local里面设置的值,这就实现了值的传递了。日志
源码分析
下面咱们从源码的角度上看一下InheritableThreadLocal
的实现,他到底是怎么作到父子线程之间线程的传递的。
咱们首先看一下Thread建立的代码。
Thread
线程初始化的代码,能够看到重点在init方法
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); }
init
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; // 1. 获取当前线程为父线程,其实就是建立这个线程的线程 Thread parent = currentThread(); // 省略代码。。。。。 // 2. 判断inheritThreadLocals 是否==true, 父节点的inheritableThreadLocals是否不为空 if (inheritThreadLocals && parent.inheritableThreadLocals != null) //3. 符合以上的话,那么建立当前线程的inheritableThreadLocals this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
步骤说明:
看着我上面标的步骤进行说明
1.获取当前线程为父线程,其实就是建立这个线程的线程
2.判断inheritThreadLocals 是否==true , 默认inheritThreadLocals就是为true, 通用的new Thread()方法,这个值就是true, 同时判断父节点的inheritableThreadLocals是否为空, 若是不为空,则说明须要进行传递。
3.在这个if里面,针对当前线程作了inheritableThreadLocals的初始化, 把父线程的值拷贝到这个里面来。
经过上面的分析,其实基本的原理都已经了解清楚了,不熟悉的能够能够本身去细细研究。
那么是否这种作法彻底能够符合咱们的需求呢? 咱们看一下下面的场景
线程池异常场景
线程池demo
/** * ce * * @author zhangyunhe * @date 2020-04-22 16:19 */ public class InteritableTest { static ThreadLocal<String> local = new InheritableThreadLocal<>(); static ExecutorService poolExecutor = Executors.newFixedThreadPool(1); public static void main(String[] args) throws ExecutionException, InterruptedException { InteritableTest test = new InteritableTest(); test.test(); } private void test() throws ExecutionException, InterruptedException { local.set("天王老子"); Future future = poolExecutor.submit(new Task("任务1")); future.get(); Future future2 = poolExecutor.submit(new Task("任务2")); future2.get(); System.out.println("父线程的值:"+local.get()); } class Task implements Runnable{ String str; Task(String str){ this.str = str; } @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+local.get()); local.set(str); System.out.println(local.get()); } } }
输出结果:
pool-1-thread-1:天王老子 任务1 pool-1-thread-1:任务1 任务2 父线程的值:天王老子
从上面能够看到,Task2执行的时候,获取到的父线程的值是Task1修改过的。 这样感受是否是就破坏了咱们的本意? 实际上,这是由于咱们使用了线程池,在池化技术里面,线程是会被复用的,当执行Task2的时候,其实是用的Task1的那个线程,那个线程已经被建立好了的,因此那里面的locals就是被Task1修改过的,那么遇到这种问题,该如何解决呢?
下一篇文章给你们介绍一个组件,在使用线程池等会池化复用线程的执行组件状况下,提供ThreadLocal值的传递功能