17.线程系列- ThreadLocal、InheritableThreadLocal(通俗易懂)

本文的内容

  1. 需要解决的问题
  2. 介绍ThreadLocal
  3. 介绍InheritableThreadLocal

需要解决的问题

目前java开发web系统一般有3层,controller,service,dao,请求到达controller。controller调用service,service调用dao,然后进行处理

我们写一个简单的列子,有3个方法分别模拟controller,service,dao。代码如下:

public class Demo {

    static AtomicInteger threadIndex = new AtomicInteger(1);

    //定义线程池
    static ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 60,
            TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("thread-" + threadIndex.getAndIncrement());
                return thread;
            });

    //定义日志
    public static void log(String msg) {
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        System.out.println("***" + System.currentTimeMillis() + ",线程名称:" + Thread.currentThread().getName() +
                ",当前执行:" + stackTrace[1] + "," + msg);
    }

    //定义dao层
    public static void dao(List<String> dataList) {
        log("执行数据库操作");
        //模拟插入数据
        for (String s : dataList) {
            log("插入数据" + s + "成功");
        }
    }

    //定义service层
    public static void service(List<String> dataList) {
        log("执行业务");
        dao(dataList);
    }

    //定义controller层
    public static void controller(List<String> dataList) {
        log("接受请求");
        service(dataList);
    }

    public static void main(String[] args) {
        //需要插入的数据
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            dataList.add("数据" + i);
        }

        //模拟5个请求
        int requestCount = 5;
        for (int i = 0; i < requestCount; i++) {
            executor.execute(() -> {
                controller(dataList);
            });
        }
        executor.shutdown();
    }
}

运行结果:

***1608447331338,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo.controller(Demo.java:48),接受请求
***1608447331338,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.controller(Demo.java:48),接受请求
***1608447331338,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.controller(Demo.java:48),接受请求
***1608447331339,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.service(Demo.java:42),执行业务
***1608447331338,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo.service(Demo.java:42),执行业务
***1608447331339,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:33),执行数据库操作
***1608447331339,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.service(Demo.java:42),执行业务
***1608447331339,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:33),执行数据库操作
***1608447331339,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据0成功
***1608447331339,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:33),执行数据库操作
***1608447331339,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据1成功
***1608447331339,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据0成功
***1608447331339,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据2成功
***1608447331339,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据0成功
***1608447331339,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据1成功
***1608447331339,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据1成功
***1608447331339,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.controller(Demo.java:48),接受请求
***1608447331339,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据2成功
***1608447331339,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据2成功
***1608447331340,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.service(Demo.java:42),执行业务
***1608447331340,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.controller(Demo.java:48),接受请求
***1608447331340,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:33),执行数据库操作
***1608447331340,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.service(Demo.java:42),执行业务
***1608447331340,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据0成功
***1608447331340,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:33),执行数据库操作
***1608447331340,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据1成功
***1608447331340,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据0成功
***1608447331340,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据2成功
***1608447331340,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据1成功
***1608447331340,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo.dao(Demo.java:36),插入数据数据2成功
Disconnected from the target VM, address: '127.0.0.1:52157', transport: 'socket'

Process finished with exit code 0

代码中调用controller,service,dao 3个方法时,来模拟处理一个请求。main方法中循环了5次模拟发起5次请求,然后交给线程池去处理请求。dao中模拟循环插入传入的dataList数据。

问题来了:开发者想看一下哪些地方耗时比较多,想通过日志来分析耗时情况,想追踪某个请求的完整日志,怎么搞?

上面的请求采用线程池的方式处理的,多个请求可能会被一个线程处理,通过日志很难看出哪些日志是同一个请求,我们能不能给请求加一个唯一标志?日志中输出这个唯一标志,当然可以。

如果我们的代码就只有上面示例这么简单,我想还是很容易的。上面就3个方法,给每个方法加个traceId参数,log方法也价格traceId参数,就解决了。

public class Dem1 {

    //定义线程名称
    static AtomicInteger threadIndex = new AtomicInteger(1);

    //定义线程池
    static ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 60,
            TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("thread-" + threadIndex.getAndIncrement());
                return thread;
            });

    //定义日志
    public static void log(String msg, String traceId) {
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        System.out.println("***" + System.currentTimeMillis() + ",traceId:" + traceId + ",线程名称:" + Thread.currentThread().getName() +
                ",当前执行:" + stackTrace[1] + "," + msg);
    }

    //定义dao层
    public static void dao(List<String> dataList, String traceId) {
        log("执行数据库操作", traceId);
        //模拟插入数据
        for (String s : dataList) {
            log("插入数据" + s + "成功", traceId);
        }
    }

    //定义service层
    public static void service(List<String> dataList, String traceId) {
        log("执行业务", traceId);
        dao(dataList, traceId);
    }

    //定义controller层
    public static void controller(List<String> dataList, String traceId) {
        log("接受请求", traceId);
        service(dataList, traceId);
    }

    public static void main(String[] args) {
        //需要插入的数据
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            dataList.add("数据" + i);
        }

        //模拟5个请求
        int requestCount = 5;
        for (int i = 0; i < requestCount; i++) {
            String traceId = String.valueOf(i);
            executor.execute(() -> {
                controller(dataList, traceId);
            });
        }
        executor.shutdown();
    }
}

输出:

***1608448168425,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Dem1.controller(Dem1.java:48),接受请求
***1608448168425,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Dem1.controller(Dem1.java:48),接受请求
***1608448168425,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.controller(Dem1.java:48),接受请求
***1608448168426,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Dem1.service(Dem1.java:42),执行业务
***1608448168425,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Dem1.service(Dem1.java:42),执行业务
***1608448168426,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:33),执行数据库操作
***1608448168426,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.service(Dem1.java:42),执行业务
***1608448168426,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:33),执行数据库操作
***1608448168426,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据0成功
***1608448168426,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据1成功
***1608448168426,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据2成功
***1608448168427,traceId:3,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.controller(Dem1.java:48),接受请求
***1608448168427,traceId:3,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.service(Dem1.java:42),执行业务
***1608448168427,traceId:3,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:33),执行数据库操作
***1608448168427,traceId:3,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据0成功
***1608448168427,traceId:3,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据1成功
***1608448168427,traceId:3,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据2成功
***1608448168427,traceId:4,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.controller(Dem1.java:48),接受请求
***1608448168426,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:33),执行数据库操作
***1608448168427,traceId:4,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.service(Dem1.java:42),执行业务
***1608448168427,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据0成功
***1608448168427,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据1成功
***1608448168427,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据2成功
***1608448168426,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据0成功
***1608448168428,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据1成功
***1608448168428,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据2成功
***1608448168427,traceId:4,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:33),执行数据库操作
***1608448168429,traceId:4,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据0成功
***1608448168429,traceId:4,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据1成功
***1608448168429,traceId:4,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Dem1.dao(Dem1.java:36),插入数据数据2成功

上面我们通过修改代码的方式,把问题解决了,但前提是你们的系统都像上面这么简单,功能很少,需要改的代码很少,可以这么去改。但事与愿违,我们的系统一般功能都是比较多的,如果我们都一个个去改,岂不是要疯掉,改代码还涉及到重新测试,风险也不可控。那有什么好办法么?

ThreadLocal

还是拿上面的问题,我们来分析一下,每个请求都是由一个线程处理的,线程就相当于一个人一样,每个请求相当于一个任务,任务来了,人来处理,处理完毕之后,再处理下一个请求任务。人身上是不是有很多口袋,人刚开始准备处理任务的时候,我们把任务的编号放在处理者的口袋中,然后处理中一路携带者,处理过程中如果需要用到这个编号,直接从口袋中获取就可以了。那么刚好java中线程设计的时候也考虑到了这些问题,Thread对象中就有很多口袋,用来放东西。Thread类中有这么一个变量:

ThreadLocal.ThreadLocalMap threadLocals = null;

这个就是用来操作Thread中所有口袋的东西,ThreadLocalMap源码中有一个数组(有兴趣的可以去看一下源码),对应处理者身上很多口袋一样,数组中的每个元素对应一个口袋。

如何来操作Thread中的这些口袋呢,java为我们提供了一个类ThreadLocal,ThreadLocal对象用来操作Thread中的某一个口袋,可以向这个口袋中放东西、获取里面的东西、清除里面的东西,这个口袋一次性只能放一个东西,重复放东西会将里面已经存在的东西覆盖掉。

常用的3个方法:

//向Thread中某个口袋中放东西
public void set(T value);
//获取这个口袋中目前放的东西
public T get();
//清空这个口袋中放的东西
public void remove()

我们使用ThreadLocal来改造一下上面的代码,如下:

public class Demo2 {

    //定义ThreadLocal
    static ThreadLocal threadLocal = new ThreadLocal();

    //定义线程名称
    static AtomicInteger threadIndex = new AtomicInteger(1);

    //定义线程池
    static ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 60,
            TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("thread-" + threadIndex.getAndIncrement());
                return thread;
            });

    //定义日志
    public static void log(String msg) {
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        System.out.println("***" + System.currentTimeMillis()+",traceId:" + threadLocal.get() + ",线程名称:" + Thread.currentThread().getName() +
                ",当前执行:" + stackTrace[1] + "," + msg);
    }

    //定义dao层
    public static void dao(List<String> dataList) {
        log("执行数据库操作");
        //模拟插入数据
        for (String s : dataList) {
            log("插入数据" + s + "成功");
        }
    }

    //定义service层
    public static void service(List<String> dataList) {
        log("执行业务");
        dao(dataList);
    }

    //定义controller层
    public static void controller(List<String> dataList) {
        log("接受请求");
        service(dataList);
    }

    public static void main(String[] args) {
        //需要插入的数据
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            dataList.add("数据" + i);
        }

        //模拟5个请求
        int requestCount = 5;
        for (int i = 0; i < requestCount; i++) {
            String traceId = String.valueOf(i);
            try {
                executor.execute(() -> {
                    threadLocal.set(traceId);
                    controller(dataList);
                });
            }finally {
                threadLocal.remove();
            }
        }
        executor.shutdown();
    }
}

输出:

***1608448461174,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.controller(Demo2.java:51),接受请求
***1608448461174,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.service(Demo2.java:45),执行业务
***1608448461174,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:36),执行数据库操作
***1608448461174,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据0成功
***1608448461174,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.controller(Demo2.java:51),接受请求
***1608448461174,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.service(Demo2.java:45),执行业务
***1608448461175,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:36),执行数据库操作
***1608448461174,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据1成功
***1608448461175,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据0成功
***1608448461175,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据1成功
***1608448461175,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据2成功
***1608448461175,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据2成功
***1608448461175,traceId:4,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.controller(Demo2.java:51),接受请求
***1608448461175,traceId:3,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.controller(Demo2.java:51),接受请求
***1608448461175,traceId:4,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.service(Demo2.java:45),执行业务
***1608448461175,traceId:3,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.service(Demo2.java:45),执行业务
***1608448461176,traceId:4,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:36),执行数据库操作
***1608448461176,traceId:3,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:36),执行数据库操作
***1608448461176,traceId:4,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据0成功
***1608448461176,traceId:3,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据0成功
***1608448461176,traceId:4,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据1成功
***1608448461176,traceId:3,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据1成功
***1608448461176,traceId:4,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据2成功
***1608448461176,traceId:3,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据2成功
***1608448461177,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo2.controller(Demo2.java:51),接受请求
***1608448461177,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo2.service(Demo2.java:45),执行业务
***1608448461177,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:36),执行数据库操作
***1608448461177,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据0成功
***1608448461177,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据1成功
***1608448461178,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo2.dao(Demo2.java:39),插入数据数据2成功

可以看出输出和刚才使用traceId参数的方式结果一致,但是却简单了很多。不用去修改controller、service、dao代码了,风险也减少了很多。

代码中创建了一个ThreadLocal,这个对象用来操作Thread中一个口袋,用这个口袋来存放tranceId。在main方法中通过ThreadLocal.set(traceId)方法将traceId放入口袋,log方法中通ThreadLocal.get()获取口袋中的traceId,最后任务处理完之后,main中的finally中调用ThreadLocal.remove();将口袋中的traceId清除。

InheritableThreadLocal

继续上面的示例,dao中循环处理dataList的内容,加入dataList处理比较耗时,我们想加快处理速度有什么办法?大家想到 了,用多线程并行处理dataList,那么我们把代码改造一下:

public class Demo3 {

    //定义ThreadLocal
    static ThreadLocal threadLocal = new ThreadLocal();

    //定义线程名称
    static AtomicInteger threadIndex = new AtomicInteger(1);

    //定义线程池
    static ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 60,
            TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("thread-" + threadIndex.getAndIncrement());
                return thread;
            });

    //定义日志
    public static void log(String msg) {
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        System.out.println("***" + System.currentTimeMillis() +
                ",traceId:" + threadLocal.get() +
                ",线程名称:" + Thread.currentThread().getName() +
                ",当前执行:" + stackTrace[1] + "," + msg);
    }

    //定义dao层
    public static void dao(List<String> dataList) {
        CountDownLatch countDownLatch = new CountDownLatch(dataList.size());
        log("执行数据库操作");
        //模拟插入数据
        for (String s : dataList) {
            new Thread(() -> {
                //模拟数据库操作耗时100毫秒
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                    log("插入数据" + s + "成功");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //定义service层
    public static void service(List<String> dataList) {
        log("执行业务");
        dao(dataList);
    }

    //定义controller层
    public static void controller(List<String> dataList) {
        log("接受请求");
        service(dataList);
    }

    public static void main(String[] args) {
        //需要插入的数据
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            dataList.add("数据" + i);
        }

        //模拟5个请求
        int requestCount = 5;
        for (int i = 0; i < requestCount; i++) {
            String traceId = String.valueOf(i);
            try {
                executor.execute(() -> {
                    threadLocal.set(traceId);
                    controller(dataList);
                });
            } finally {
                threadLocal.remove();
            }
        }
        executor.shutdown();
    }
}

输出:

***1608514779041,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo3.controller(Demo3.java:70),接受请求
***1608514779042,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo3.controller(Demo3.java:70),接受请求
***1608514779041,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo3.controller(Demo3.java:70),接受请求
***1608514779042,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo3.service(Demo3.java:64),执行业务
***1608514779042,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo3.service(Demo3.java:64),执行业务
***1608514779042,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo3.service(Demo3.java:64),执行业务
***1608514779042,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo3.dao(Demo3.java:40),执行数据库操作
***1608514779042,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo3.dao(Demo3.java:40),执行数据库操作
***1608514779042,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo3.dao(Demo3.java:40),执行数据库操作
***1608514779145,traceId:null,线程名称:Thread-7,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据1成功
***1608514779145,traceId:null,线程名称:Thread-5,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据0成功
***1608514779145,traceId:null,线程名称:Thread-4,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据0成功
***1608514779145,traceId:null,线程名称:Thread-8,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据1成功
***1608514779145,traceId:null,线程名称:Thread-6,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据1成功
***1608514779145,traceId:null,线程名称:Thread-9,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据2成功
***1608514779145,traceId:null,线程名称:Thread-3,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据0成功
***1608514779146,traceId:3,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo3.controller(Demo3.java:70),接受请求
***1608514779146,traceId:null,线程名称:Thread-10,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据2成功
***1608514779146,traceId:null,线程名称:Thread-11,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据2成功
***1608514779146,traceId:4,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo3.controller(Demo3.java:70),接受请求
***1608514779146,traceId:3,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo3.service(Demo3.java:64),执行业务
***1608514779146,traceId:3,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo3.dao(Demo3.java:40),执行数据库操作
***1608514779146,traceId:4,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo3.service(Demo3.java:64),执行业务
***1608514779146,traceId:4,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo3.dao(Demo3.java:40),执行数据库操作
***1608514779248,traceId:null,线程名称:Thread-17,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据2成功
***1608514779248,traceId:null,线程名称:Thread-13,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据1成功
***1608514779248,traceId:null,线程名称:Thread-14,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据0成功
***1608514779248,traceId:null,线程名称:Thread-15,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据2成功
***1608514779248,traceId:null,线程名称:Thread-16,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据1成功
***1608514779248,traceId:null,线程名称:Thread-12,当前执行:com.example.thread.threadLocal.Demo3.lambda$dao$1(Demo3.java:47),插入数据数据0成功
Disconnected from the target VM, address: '127.0.0.1:54063', transport: 'socket'

Process finished with exit code 0

看一下上面的输出,有些traceId为null,这是为什么呢?这是因为dao中为了提升处理速度,创建了子线程来并行处理,子线程调用log的时候,去自己的存放traceId的口袋中拿去东西,肯定是空的了。

那有什么办法么?可不可以这样?

父线程相当于主管,子线程相当于干活的小弟,主管让小弟们干活的时候,将自己兜里面的东西复制一份给小弟们使用,主管兜里面可能有很多牛逼的工具,为了提升小弟们的工作效率,给小弟们都复制一个,丢到小弟们的兜里,然后小弟就可以从自己的兜里拿去这些东西使用了,也可以清空自己兜里面的东西。

Thread对象中有个inheritableThreadLocals变量,代码如下:

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

inheritableThreadLocals相当于线程中另外一种兜,这种兜有什么特性呢?当创建子线程的时候,子线程会将父线程这种类型兜的东西全部复制一份放到自己的inheritableThreadLocals兜中,使用InheritableThreadLocal对象可以操作线程中的inheritableThreadLocals。

常用的方法有3个:

//向Thread中某个口袋中放东西
public void set(T value);
//获取这个口袋中目前放的东西
public T get();
//清空这个口袋中放的东西
public void remove()

我们对上面的示例进行改造:

//定义ThreadLocal
    static InheritableThreadLocal threadLocal = new InheritableThreadLocal();

我们只需要把ThreadLocal替换成InheritableThreadLocal。

输出:

***1608515192534,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo4.controller(Demo4.java:70),接受请求
***1608515192534,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo4.service(Demo4.java:64),执行业务
***1608515192535,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo4.controller(Demo4.java:70),接受请求
***1608515192535,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo4.service(Demo4.java:64),执行业务
***1608515192535,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo4.controller(Demo4.java:70),接受请求
***1608515192535,traceId:0,线程名称:thread-1,当前执行:com.example.thread.threadLocal.Demo4.dao(Demo4.java:40),执行数据库操作
***1608515192535,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo4.service(Demo4.java:64),执行业务
***1608515192535,traceId:2,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo4.dao(Demo4.java:40),执行数据库操作
***1608515192535,traceId:1,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo4.dao(Demo4.java:40),执行数据库操作
***1608515192637,traceId:2,线程名称:Thread-7,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据1成功
***1608515192637,traceId:0,线程名称:Thread-9,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据1成功
***1608515192637,traceId:2,线程名称:Thread-4,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据0成功
***1608515192637,traceId:0,线程名称:Thread-6,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据0成功
***1608515192637,traceId:1,线程名称:Thread-5,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据1成功
***1608515192637,traceId:1,线程名称:Thread-3,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据0成功
***1608515192638,traceId:2,线程名称:Thread-10,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据2成功
***1608515192638,traceId:1,线程名称:Thread-8,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据2成功
***1608515192638,traceId:0,线程名称:Thread-11,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据2成功
***1608515192638,traceId:4,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo4.controller(Demo4.java:70),接受请求
***1608515192638,traceId:3,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo4.controller(Demo4.java:70),接受请求
***1608515192638,traceId:4,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo4.service(Demo4.java:64),执行业务
***1608515192638,traceId:3,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo4.service(Demo4.java:64),执行业务
***1608515192638,traceId:4,线程名称:thread-2,当前执行:com.example.thread.threadLocal.Demo4.dao(Demo4.java:40),执行数据库操作
***1608515192638,traceId:3,线程名称:thread-3,当前执行:com.example.thread.threadLocal.Demo4.dao(Demo4.java:40),执行数据库操作
***1608515192740,traceId:3,线程名称:Thread-15,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据1成功
***1608515192740,traceId:3,线程名称:Thread-17,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据2成功
***1608515192740,traceId:4,线程名称:Thread-12,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据0成功
***1608515192740,traceId:4,线程名称:Thread-16,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据2成功
***1608515192740,traceId:4,线程名称:Thread-14,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据1成功
***1608515192740,traceId:3,线程名称:Thread-13,当前执行:com.example.thread.threadLocal.Demo4.lambda$dao$1(Demo4.java:47),插入数据数据0成功

输出中都有traceId了,和期望的结果一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值