多线程篇-父子线程的上下文传递

欢迎关注公众号【sharedCode】致力于主流中间件的源码分析, 我的网站:https://www.shared-code.com/java

前言

在实际的工做中,有些时候咱们会遇到一些线程之间共享数据的问题,好比一下几个典型的业务场景,举几个例子,你们可以更加直观的感觉到异步

  1. 分布式链路追踪系统分布式

    这个没必要多说,若是咱们须要记录系统的每一个调用链路,那么每个子系统里面,若是调用了异步线程来作处理的话,那么相似这种链路是否是须要收集起来呢?ide

  2. 日志收集记录系统上下文工具

    在实际的日志打印记录中,一个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值的传递功能

相关文章
相关标签/搜索