本文的内容
- 需要解决的问题
- 介绍ThreadLocal
- 介绍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了,和期望的结果一致。