文章目录

  • 1.概述
  • 2.需要解决的问题
  • ThreadLocal
  • InheritableThreadLocal

1.概述

转载:添加链接描述

2.需要解决的问题

我们还是以解决问题的方式来引出ThreadLocal、InheritableThreadLocal,这样印象会深刻一些。

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

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

package com.itsoku.chat24;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** 跟着阿里p7学并发,微信公众号:javacode2018*/
public class Demo1 {static AtomicInteger threadIndex = new AtomicInteger(1);//创建处理请求的线程池子static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,3,60,TimeUnit.SECONDS,new LinkedBlockingDeque<>(),r -> {Thread thread = new Thread(r);thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());return thread;});//记录日志public static void log(String msg) {StackTraceElement stack[] = (new Throwable()).getStackTrace();System.out.println("****" + System.currentTimeMillis() + ",[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);}//模拟controllerpublic static void controller(List<String> dataList) {log("接受请求");service(dataList);}//模拟servicepublic static void service(List<String> dataList) {log("执行业务");dao(dataList);}//模拟daopublic static void dao(List<String> dataList) {log("执行数据库操作");//模拟插入数据for (String s : dataList) {log("插入数据" + s + "成功");}}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++) {disposeRequestExecutor.execute(() -> {controller(dataList);});}disposeRequestExecutor.shutdown();}
}

运行结果:

****1565338891286,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
****1565338891286,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
****1565338891287,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
****1565338891287,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
****1565338891287,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
****1565338891287,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
****1565338891287,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功

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

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

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

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

package com.itsoku.chat24;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** 跟着阿里p7学并发,微信公众号:javacode2018*/
public class Demo2 {static AtomicInteger threadIndex = new AtomicInteger(1);//创建处理请求的线程池子static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,3,60,TimeUnit.SECONDS,new LinkedBlockingDeque<>(),r -> {Thread thread = new Thread(r);thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());return thread;});//记录日志public static void log(String msg, String traceId) {StackTraceElement stack[] = (new Throwable()).getStackTrace();System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);}//模拟controllerpublic static void controller(List<String> dataList, String traceId) {log("接受请求", traceId);service(dataList, traceId);}//模拟servicepublic static void service(List<String> dataList, String traceId) {log("执行业务", traceId);dao(dataList, traceId);}//模拟daopublic static void dao(List<String> dataList, String traceId) {log("执行数据库操作", traceId);//模拟插入数据for (String s : dataList) {log("插入数据" + s + "成功", 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);disposeRequestExecutor.execute(() -> {controller(dataList, traceId);});}disposeRequestExecutor.shutdown();}
}

输出:

****1565339559773[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
****1565339559773[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
****1565339559773[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
****1565339559774[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
****1565339559774[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
****1565339559774[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
****1565339559774[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
****1565339559774[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
****1565339559775[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
****1565339559775[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
****1565339559775[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功

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

ThreadLocal

相关文章:【Java】Java中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来改造一下上面的代码,如下:

package com.itsoku.chat24;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** 跟着阿里p7学并发,微信公众号:javacode2018*/
public class Demo3 {//创建一个操作Thread中存放请求任务追踪id口袋的对象static ThreadLocal<String> traceIdKD = new ThreadLocal<>();static AtomicInteger threadIndex = new AtomicInteger(1);//创建处理请求的线程池子static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,3,60,TimeUnit.SECONDS,new LinkedBlockingDeque<>(),r -> {Thread thread = new Thread(r);thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());return thread;});//记录日志public static void log(String msg) {StackTraceElement stack[] = (new Throwable()).getStackTrace();//获取当前线程存放tranceId口袋中的内容String traceId = traceIdKD.get();System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);}//模拟controllerpublic static void controller(List<String> dataList) {log("接受请求");service(dataList);}//模拟servicepublic static void service(List<String> dataList) {log("执行业务");dao(dataList);}//模拟daopublic static void dao(List<String> dataList) {log("执行数据库操作");//模拟插入数据for (String s : dataList) {log("插入数据" + s + "成功");}}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);disposeRequestExecutor.execute(() -> {//把traceId放入口袋中traceIdKD.set(traceId);try {controller(dataList);} finally {//将tranceId从口袋中移除traceIdKD.remove();}});}disposeRequestExecutor.shutdown();}
}

输出:

****1565339644214[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644214[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644214[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644214[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644214[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644214[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644215[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644215[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644215[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
****1565339644215[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
****1565339644215[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644215[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功

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

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

ThreadLocal的官方API解释为:

“该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。”

InheritableThreadLocal

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

pa

ckage com.itsoku.chat24;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** 跟着阿里p7学并发,微信公众号:javacode2018*/
public class Demo4 {//创建一个操作Thread中存放请求任务追踪id口袋的对象static ThreadLocal<String> traceIdKD = new ThreadLocal<>();static AtomicInteger threadIndex = new AtomicInteger(1);//创建处理请求的线程池子static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,3,60,TimeUnit.SECONDS,new LinkedBlockingDeque<>(),r -> {Thread thread = new Thread(r);thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());return thread;});//记录日志public static void log(String msg) {StackTraceElement stack[] = (new Throwable()).getStackTrace();//获取当前线程存放tranceId口袋中的内容String traceId = traceIdKD.get();System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);}//模拟controllerpublic static void controller(List<String> dataList) {log("接受请求");service(dataList);}//模拟servicepublic static void service(List<String> dataList) {log("执行业务");dao(dataList);}//模拟daopublic static void dao(List<String> dataList) {CountDownLatch countDownLatch = new CountDownLatch(dataList.size());log("执行数据库操作");String threadName = Thread.currentThread().getName();//模拟插入数据for (String s : dataList) {new Thread(() -> {try {//模拟数据库操作耗时100毫秒TimeUnit.MILLISECONDS.sleep(100);log("插入数据" + s + "成功,主线程:" + threadName);} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}}).start();}//等待上面的dataList处理完毕try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}}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);disposeRequestExecutor.execute(() -> {//把traceId放入口袋中traceIdKD.set(traceId);try {controller(dataList);} finally {//将tranceId从口袋中移除traceIdKD.remove();}});}disposeRequestExecutor.shutdown();}
}

输出:

****1565339904279[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565339904279[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565339904279[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565339904279[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565339904279[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565339904279[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565339904279[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565339904279[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565339904279[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565339904281[traceId:null],[线程:Thread-3],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-1
****1565339904281[traceId:null],[线程:Thread-5],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-2
****1565339904281[traceId:null],[线程:Thread-4],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-3
****1565339904281[traceId:null],[线程:Thread-6],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据1成功,主线程:disposeRequestThread-3
****1565339904281[traceId:null],[线程:Thread-9],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-3
****1565339904282[traceId:null],[线程:Thread-8],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据1成功,主线程:disposeRequestThread-1
****1565339904282[traceId:null],[线程:Thread-10],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-1
****1565339904282[traceId:3],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565339904282[traceId:null],[线程:Thread-7],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据1成功,主线程:disposeRequestThread-2
****1565339904282[traceId:null],[线程:Thread-11],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-2
****1565339904282[traceId:3],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565339904282[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565339904283[traceId:3],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565339904283[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565339904283[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565339904283[traceId:null],[线程:Thread-12],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-3
****1565339904283[traceId:null],[线程:Thread-13],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据1成功,主线程:disposeRequestThread-3
****1565339904283[traceId:null],[线程:Thread-14],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-1
****1565339904284[traceId:null],[线程:Thread-15],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-3
****1565339904284[traceId:null],[线程:Thread-17],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-1
****1565339904284[traceId:null],[线程:Thread-16],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据1成功,主线程:disposeRequestThread-1

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

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

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

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

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

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

InheritableThreadLocal常用的方法也有3个:

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

使用InheritableThreadLocal解决上面子线程中无法输出traceId的问题,只需要将上一个示例代码中的ThreadLocal替换成InheritableThreadLocal即可,代码如下:

package com.itsoku.chat24;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** 跟着阿里p7学并发,微信公众号:javacode2018*/
public class Demo4 {//创建一个操作Thread中存放请求任务追踪id口袋的对象,子线程可以继承父线程中内容static InheritableThreadLocal<String> traceIdKD = new InheritableThreadLocal<>();static AtomicInteger threadIndex = new AtomicInteger(1);//创建处理请求的线程池子static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,3,60,TimeUnit.SECONDS,new LinkedBlockingDeque<>(),r -> {Thread thread = new Thread(r);thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());return thread;});//记录日志public static void log(String msg) {StackTraceElement stack[] = (new Throwable()).getStackTrace();//获取当前线程存放tranceId口袋中的内容String traceId = traceIdKD.get();System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);}//模拟controllerpublic static void controller(List<String> dataList) {log("接受请求");service(dataList);}//模拟servicepublic static void service(List<String> dataList) {log("执行业务");dao(dataList);}//模拟daopublic static void dao(List<String> dataList) {CountDownLatch countDownLatch = new CountDownLatch(dataList.size());log("执行数据库操作");String threadName = Thread.currentThread().getName();//模拟插入数据for (String s : dataList) {new Thread(() -> {try {//模拟数据库操作耗时100毫秒TimeUnit.MILLISECONDS.sleep(100);log("插入数据" + s + "成功,主线程:" + threadName);} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}}).start();}//等待上面的dataList处理完毕try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}}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);disposeRequestExecutor.execute(() -> {//把traceId放入口袋中traceIdKD.set(traceId);try {controller(dataList);} finally {//将tranceId从口袋中移除traceIdKD.remove();}});}disposeRequestExecutor.shutdown();}
}

输出:

****1565341611454[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565341611454[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565341611454[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565341611454[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565341611454[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565341611454[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565341611454[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565341611454[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565341611454[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565341611557[traceId:2],[线程:Thread-5],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据0成功,主线程:disposeRequestThread-3
****1565341611557[traceId:0],[线程:Thread-4],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据0成功,主线程:disposeRequestThread-1
****1565341611557[traceId:1],[线程:Thread-11],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据2成功,主线程:disposeRequestThread-2
****1565341611557[traceId:1],[线程:Thread-3],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据0成功,主线程:disposeRequestThread-2
****1565341611557[traceId:1],[线程:Thread-8],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据1成功,主线程:disposeRequestThread-2
****1565341611557[traceId:0],[线程:Thread-6],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据1成功,主线程:disposeRequestThread-1
****1565341611557[traceId:0],[线程:Thread-10],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据2成功,主线程:disposeRequestThread-1
****1565341611557[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565341611557[traceId:2],[线程:Thread-9],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据2成功,主线程:disposeRequestThread-3
****1565341611558[traceId:2],[线程:Thread-7],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据1成功,主线程:disposeRequestThread-3
****1565341611557[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565341611557[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
****1565341611558[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565341611558[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
****1565341611558[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
****1565341611659[traceId:3],[线程:Thread-15],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据2成功,主线程:disposeRequestThread-2
****1565341611659[traceId:4],[线程:Thread-14],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据0成功,主线程:disposeRequestThread-1
****1565341611659[traceId:3],[线程:Thread-13],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据1成功,主线程:disposeRequestThread-2
****1565341611659[traceId:3],[线程:Thread-12],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据0成功,主线程:disposeRequestThread-2
****1565341611660[traceId:4],[线程:Thread-16],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据1成功,主线程:disposeRequestThread-1
****1565341611660[traceId:4],[线程:Thread-17],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:64):插入数据数据2成功,主线程:disposeRequestThread-1

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

希望通过这篇文章可以学会使用InheritableThreadLocal和InheritableThreadLocal。有问题可以加我微信itsoku交流,也可以留言,谢谢。

【高并发】ThreadLocal、InheritableThreadLocal相关推荐

  1. shell 获取命令执行结果_java高并发系列 第31天:获取线程执行结果,这6种方法你都知道?...

    这是java高并发系列第31篇. 环境:jdk1.8. java高并发系列已经学了不少东西了,本篇文章,我们用前面学的知识来实现一个需求: 在一个线程中需要获取其他线程的执行结果,能想到几种方式?各有 ...

  2. 《深入理解高并发编程》学习笔记

    文章目录 幕后黑手 可见性 原子性 有序性 解决办法 java内存模型 volatile Happens-Before原则 程序次序规则 volatile变量规则 传递规则 锁定规则 线程启动规则 线 ...

  3. Java高并发革命!JDK19新特性——虚拟线程(Virtual Threads)

    介绍 虚拟线程具有和 Go 语言的 goroutines 和 Erlang 语言的进程类似的实现方式,它们是用户模式(user-mode)线程的一种形式. 在过去 Java 中常常使用线程池来进行平台 ...

  4. 【高并发】高并发分布式锁架构解密,不是所有的锁都是分布式锁!!

    来自:冰河技术 写在前面 最近,很多小伙伴留言说,在学习高并发编程时,不太明白分布式锁是用来解决什么问题的,还有不少小伙伴甚至连分布式锁是什么都不太明白.明明在生产环境上使用了自己开发的分布式锁,为什 ...

  5. 一篇博客带你轻松应对java面试中的多线程与高并发

    1. Java线程的创建方式 (1)继承thread类 thread类本质是实现了runnable接口的一个实例,代表线程的一个实例.启动线程的方式start方法.start是一个本地方法,执行后,执 ...

  6. Java 面试知识点解析(二)——高并发编程篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  7. 如何设计真正高性能高并发分布式系统(万字长文)

    "世间可称之为天经地义的事情没几样,复杂的互联网架构也是如此,万丈高楼平地起,架构都是演变而来,那么演变的本质是什么?" - 1 - 引子 软件复杂性来源于几个方面:高并发.高性能 ...

  8. 沙雕同事一来公司就把高并发的程序搞崩了,我心态也蹦了!

    最近有位粉丝刚从阿里面试回来,分享了一些面试题!测测你能完美的回答几道呢? 有没有一种一定能保证线程安全的代码写法?(偷偷告诉你,真的有!) 自定义线程池有7个参数,他们分别是什么意思? 为什么阿里规 ...

  9. java高并发(一)导学

    现在准备系统学习java高并发与多线程相关知识. 首先了解一下我们这一套知识的学习思路: 并发与高并发相关概念 CPU多级缓存 缓存一致性 乱序执行优化 java内存模型 JMM规定.抽象结构 同步操 ...

最新文章

  1. Exchange企业实战技巧(3)配置Exchange证书
  2. 山石网科-Hillstone-IPsec V_P_N常见故障debug排错心得终结版
  3. 计算机专业python教材_计算机专业几本必看的书!
  4. 如何给textbox中的文本设置垂直对齐,以及右对齐
  5. 学习笔记(十八)——MongoDB(CRUD)与Python交互
  6. Java项目 常用包的命名及理解【dao包、domain包、service包、utils包、web包、impl包】
  7. docker容器化python开发环境_如何 Docker 化 Python Django 应用程序
  8. 新版“峡谷第一美”妲己尾巴毛发制作分享
  9. 60、二叉搜索树的第k个结点
  10. MySQL——Caused by: java.sql.SQLException: Unknown system variable ‘transaction_isolation‘
  11. activiti 定时任务和线程池
  12. Atitit 返回http500返回码,以及自定义返回提示 目录 1. 可以看到从php发送http响应代码的三种方法: 1 1.1. Header(HTTP/1.1 404 Not Found
  13. System.Configuration.ConfigurationErrorsException:“配置系统未能初始化”
  14. Sketch 55 for mac(矢量绘图软件) v55.2中文永久激活版
  15. elasticsearch 分片(Shards)的理解
  16. 层次聚类sklearn.cluster包AgglomerativeClustering
  17. 【微信公众号】获取用户信息
  18. 【控制】Matlab模拟汽车动力学分析系统
  19. “三通”搭桥 两岸IT产业迎机遇
  20. python requests下载网页_python爬虫 requests-html的使用

热门文章

  1. TechWeb:转载合作须知!
  2. 姚安娜首个代言摩卡汽车人舞蹈短片出炉
  3. 比特币价格疯涨!特斯拉或将支持比特币付款
  4. 菜鸟CEO万霖:双11包裹量将继续增长 已建立更高效的物流枢纽
  5. 陌陌宣布由总裁兼COO王力担任公司新任CEO
  6. “苹果税”猛于虎惹众怒,库克:我们是不会让步的
  7. 刘强东夫妇向英国捐赠大量防疫物资:在英华侨及留学生可免费认领
  8. iPhone 12性能暴增!将再次与安卓拉开距离
  9. 华为Mate X 5G被黄牛炒出天价:这价格都能买辆车了!
  10. OPPO K3将登陆印度市场 高性价比能否占据一席之地