Java高并发专题之24、ThreadLocal、InheritableThreadLocal

本文内容

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

需要解决的问题

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

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

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

  1. package com.itsoku.chat24;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.concurrent.LinkedBlockingDeque;
  5. import java.util.concurrent.ThreadPoolExecutor;
  6. import java.util.concurrent.TimeUnit;
  7. import java.util.concurrent.atomic.AtomicInteger;
  8. /**
  9. * 跟着阿里p7学并发,微信公众号:javacode2018
  10. */
  11. public class Demo1 {
  12. static AtomicInteger threadIndex = new AtomicInteger(1);
  13. //创建处理请求的线程池子
  14. static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
  15. 3,
  16. 60,
  17. TimeUnit.SECONDS,
  18. new LinkedBlockingDeque<>(),
  19. r -> {
  20. Thread thread = new Thread(r);
  21. thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
  22. return thread;
  23. });
  24. //记录日志
  25. public static void log(String msg) {
  26. StackTraceElement stack[] = (new Throwable()).getStackTrace();
  27. System.out.println("****" + System.currentTimeMillis() + ",[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
  28. }
  29. //模拟controller
  30. public static void controller(List<String> dataList) {
  31. log("接受请求");
  32. service(dataList);
  33. }
  34. //模拟service
  35. public static void service(List<String> dataList) {
  36. log("执行业务");
  37. dao(dataList);
  38. }
  39. //模拟dao
  40. public static void dao(List<String> dataList) {
  41. log("执行数据库操作");
  42. //模拟插入数据
  43. for (String s : dataList) {
  44. log("插入数据" + s + "成功");
  45. }
  46. }
  47. public static void main(String[] args) {
  48. //需要插入的数据
  49. List<String> dataList = new ArrayList<>();
  50. for (int i = 0; i < 3; i++) {
  51. dataList.add("数据" + i);
  52. }
  53. //模拟5个请求
  54. int requestCount = 5;
  55. for (int i = 0; i < requestCount; i++) {
  56. disposeRequestExecutor.execute(() -> {
  57. controller(dataList);
  58. });
  59. }
  60. disposeRequestExecutor.shutdown();
  61. }
  62. }

运行结果:

  1. ****1565338891286,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
  2. ****1565338891286,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
  3. ****1565338891287,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
  4. ****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
  5. ****1565338891287,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
  6. ****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
  7. ****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
  8. ****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
  9. ****1565338891287,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
  10. ****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
  11. ****1565338891287,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
  12. ****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
  13. ****1565338891287,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
  14. ****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
  15. ****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
  16. ****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
  17. ****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
  18. ****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
  19. ****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
  20. ****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
  21. ****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
  22. ****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
  23. ****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
  24. ****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
  25. ****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
  26. ****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
  27. ****1565338891289,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
  28. ****1565338891289,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
  29. ****1565338891289,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
  30. ****1565338891289,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功

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

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

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

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

  1. package com.itsoku.chat24;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.concurrent.LinkedBlockingDeque;
  5. import java.util.concurrent.ThreadPoolExecutor;
  6. import java.util.concurrent.TimeUnit;
  7. import java.util.concurrent.atomic.AtomicInteger;
  8. /**
  9. * 跟着阿里p7学并发,微信公众号:javacode2018
  10. */
  11. public class Demo2 {
  12. static AtomicInteger threadIndex = new AtomicInteger(1);
  13. //创建处理请求的线程池子
  14. static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
  15. 3,
  16. 60,
  17. TimeUnit.SECONDS,
  18. new LinkedBlockingDeque<>(),
  19. r -> {
  20. Thread thread = new Thread(r);
  21. thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
  22. return thread;
  23. });
  24. //记录日志
  25. public static void log(String msg, String traceId) {
  26. StackTraceElement stack[] = (new Throwable()).getStackTrace();
  27. System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
  28. }
  29. //模拟controller
  30. public static void controller(List<String> dataList, String traceId) {
  31. log("接受请求", traceId);
  32. service(dataList, traceId);
  33. }
  34. //模拟service
  35. public static void service(List<String> dataList, String traceId) {
  36. log("执行业务", traceId);
  37. dao(dataList, traceId);
  38. }
  39. //模拟dao
  40. public static void dao(List<String> dataList, String traceId) {
  41. log("执行数据库操作", traceId);
  42. //模拟插入数据
  43. for (String s : dataList) {
  44. log("插入数据" + s + "成功", traceId);
  45. }
  46. }
  47. public static void main(String[] args) {
  48. //需要插入的数据
  49. List<String> dataList = new ArrayList<>();
  50. for (int i = 0; i < 3; i++) {
  51. dataList.add("数据" + i);
  52. }
  53. //模拟5个请求
  54. int requestCount = 5;
  55. for (int i = 0; i < requestCount; i++) {
  56. String traceId = String.valueOf(i);
  57. disposeRequestExecutor.execute(() -> {
  58. controller(dataList, traceId);
  59. });
  60. }
  61. disposeRequestExecutor.shutdown();
  62. }
  63. }

输出:

  1. ****1565339559773[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
  2. ****1565339559773[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
  3. ****1565339559773[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
  4. ****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
  5. ****1565339559774[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
  6. ****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
  7. ****1565339559774[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
  8. ****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
  9. ****1565339559774[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
  10. ****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
  11. ****1565339559774[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
  12. ****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
  13. ****1565339559774[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
  14. ****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
  15. ****1565339559775[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
  16. ****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
  17. ****1565339559775[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
  18. ****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
  19. ****1565339559775[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
  20. ****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
  21. ****1565339559775[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
  22. ****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
  23. ****1565339559775[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
  24. ****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
  25. ****1565339559775[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
  26. ****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
  27. ****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
  28. ****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
  29. ****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
  30. ****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功

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

ThreadLocal

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

  1. ThreadLocal.ThreadLocalMap threadLocals = null;

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

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

常用的3个方法:

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

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

  1. package com.itsoku.chat24;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.concurrent.LinkedBlockingDeque;
  5. import java.util.concurrent.ThreadPoolExecutor;
  6. import java.util.concurrent.TimeUnit;
  7. import java.util.concurrent.atomic.AtomicInteger;
  8. /**
  9. * 跟着阿里p7学并发,微信公众号:javacode2018
  10. */
  11. public class Demo3 {
  12. //创建一个操作Thread中存放请求任务追踪id口袋的对象
  13. static ThreadLocal<String> traceIdKD = new ThreadLocal<>();
  14. static AtomicInteger threadIndex = new AtomicInteger(1);
  15. //创建处理请求的线程池子
  16. static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
  17. 3,
  18. 60,
  19. TimeUnit.SECONDS,
  20. new LinkedBlockingDeque<>(),
  21. r -> {
  22. Thread thread = new Thread(r);
  23. thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
  24. return thread;
  25. });
  26. //记录日志
  27. public static void log(String msg) {
  28. StackTraceElement stack[] = (new Throwable()).getStackTrace();
  29. //获取当前线程存放tranceId口袋中的内容
  30. String traceId = traceIdKD.get();
  31. System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
  32. }
  33. //模拟controller
  34. public static void controller(List<String> dataList) {
  35. log("接受请求");
  36. service(dataList);
  37. }
  38. //模拟service
  39. public static void service(List<String> dataList) {
  40. log("执行业务");
  41. dao(dataList);
  42. }
  43. //模拟dao
  44. public static void dao(List<String> dataList) {
  45. log("执行数据库操作");
  46. //模拟插入数据
  47. for (String s : dataList) {
  48. log("插入数据" + s + "成功");
  49. }
  50. }
  51. public static void main(String[] args) {
  52. //需要插入的数据
  53. List<String> dataList = new ArrayList<>();
  54. for (int i = 0; i < 3; i++) {
  55. dataList.add("数据" + i);
  56. }
  57. //模拟5个请求
  58. int requestCount = 5;
  59. for (int i = 0; i < requestCount; i++) {
  60. String traceId = String.valueOf(i);
  61. disposeRequestExecutor.execute(() -> {
  62. //把traceId放入口袋中
  63. traceIdKD.set(traceId);
  64. try {
  65. controller(dataList);
  66. } finally {
  67. //将tranceId从口袋中移除
  68. traceIdKD.remove();
  69. }
  70. });
  71. }
  72. disposeRequestExecutor.shutdown();
  73. }
  74. }

输出:

  1. ****1565339644214[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
  2. ****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
  3. ****1565339644214[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
  4. ****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
  5. ****1565339644214[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
  6. ****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
  7. ****1565339644214[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
  8. ****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
  9. ****1565339644214[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
  10. ****1565339644214[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
  11. ****1565339644215[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
  12. ****1565339644215[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
  13. ****1565339644215[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
  14. ****1565339644215[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
  15. ****1565339644215[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
  16. ****1565339644215[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
  17. ****1565339644215[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
  18. ****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
  19. ****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
  20. ****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
  21. ****1565339644215[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
  22. ****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
  23. ****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
  24. ****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
  25. ****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
  26. ****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
  27. ****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
  28. ****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
  29. ****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
  30. ****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,那么我们把代码改一下,如下:

  1. package com.itsoku.chat24;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.LinkedBlockingDeque;
  6. import java.util.concurrent.ThreadPoolExecutor;
  7. import java.util.concurrent.TimeUnit;
  8. import java.util.concurrent.atomic.AtomicInteger;
  9. /**
  10. * 跟着阿里p7学并发,微信公众号:javacode2018
  11. */
  12. public class Demo4 {
  13. //创建一个操作Thread中存放请求任务追踪id口袋的对象
  14. static ThreadLocal<String> traceIdKD = new ThreadLocal<>();
  15. static AtomicInteger threadIndex = new AtomicInteger(1);
  16. //创建处理请求的线程池子
  17. static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
  18. 3,
  19. 60,
  20. TimeUnit.SECONDS,
  21. new LinkedBlockingDeque<>(),
  22. r -> {
  23. Thread thread = new Thread(r);
  24. thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
  25. return thread;
  26. });
  27. //记录日志
  28. public static void log(String msg) {
  29. StackTraceElement stack[] = (new Throwable()).getStackTrace();
  30. //获取当前线程存放tranceId口袋中的内容
  31. String traceId = traceIdKD.get();
  32. System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
  33. }
  34. //模拟controller
  35. public static void controller(List<String> dataList) {
  36. log("接受请求");
  37. service(dataList);
  38. }
  39. //模拟service
  40. public static void service(List<String> dataList) {
  41. log("执行业务");
  42. dao(dataList);
  43. }
  44. //模拟dao
  45. public static void dao(List<String> dataList) {
  46. CountDownLatch countDownLatch = new CountDownLatch(dataList.size());
  47. log("执行数据库操作");
  48. String threadName = Thread.currentThread().getName();
  49. //模拟插入数据
  50. for (String s : dataList) {
  51. new Thread(() -> {
  52. try {
  53. //模拟数据库操作耗时100毫秒
  54. TimeUnit.MILLISECONDS.sleep(100);
  55. log("插入数据" + s + "成功,主线程:" + threadName);
  56. } catch (InterruptedException e) {
  57. e.printStackTrace();
  58. } finally {
  59. countDownLatch.countDown();
  60. }
  61. }).start();
  62. }
  63. //等待上面的dataList处理完毕
  64. try {
  65. countDownLatch.await();
  66. } catch (InterruptedException e) {
  67. e.printStackTrace();
  68. }
  69. }
  70. public static void main(String[] args) {
  71. //需要插入的数据
  72. List<String> dataList = new ArrayList<>();
  73. for (int i = 0; i < 3; i++) {
  74. dataList.add("数据" + i);
  75. }
  76. //模拟5个请求
  77. int requestCount = 5;
  78. for (int i = 0; i < requestCount; i++) {
  79. String traceId = String.valueOf(i);
  80. disposeRequestExecutor.execute(() -> {
  81. //把traceId放入口袋中
  82. traceIdKD.set(traceId);
  83. try {
  84. controller(dataList);
  85. } finally {
  86. //将tranceId从口袋中移除
  87. traceIdKD.remove();
  88. }
  89. });
  90. }
  91. disposeRequestExecutor.shutdown();
  92. }
  93. }

输出:

  1. ****1565339904279[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
  2. ****1565339904279[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
  3. ****1565339904279[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
  4. ****1565339904279[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
  5. ****1565339904279[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
  6. ****1565339904279[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
  7. ****1565339904279[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
  8. ****1565339904279[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
  9. ****1565339904279[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
  10. ****1565339904281[traceId:null],[线程:Thread-3],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-1
  11. ****1565339904281[traceId:null],[线程:Thread-5],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-2
  12. ****1565339904281[traceId:null],[线程:Thread-4],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-3
  13. ****1565339904281[traceId:null],[线程:Thread-6],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据1成功,主线程:disposeRequestThread-3
  14. ****1565339904281[traceId:null],[线程:Thread-9],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-3
  15. ****1565339904282[traceId:null],[线程:Thread-8],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据1成功,主线程:disposeRequestThread-1
  16. ****1565339904282[traceId:null],[线程:Thread-10],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-1
  17. ****1565339904282[traceId:3],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
  18. ****1565339904282[traceId:null],[线程:Thread-7],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据1成功,主线程:disposeRequestThread-2
  19. ****1565339904282[traceId:null],[线程:Thread-11],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-2
  20. ****1565339904282[traceId:3],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
  21. ****1565339904282[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.controller(Demo4.java:42):接受请求
  22. ****1565339904283[traceId:3],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
  23. ****1565339904283[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.service(Demo4.java:48):执行业务
  24. ****1565339904283[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo4.dao(Demo4.java:56):执行数据库操作
  25. ****1565339904283[traceId:null],[线程:Thread-12],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-3
  26. ****1565339904283[traceId:null],[线程:Thread-13],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据1成功,主线程:disposeRequestThread-3
  27. ****1565339904283[traceId:null],[线程:Thread-14],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据0成功,主线程:disposeRequestThread-1
  28. ****1565339904284[traceId:null],[线程:Thread-15],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-3
  29. ****1565339904284[traceId:null],[线程:Thread-17],com.itsoku.chat24.Demo4.lambda$dao$1(Demo4.java:62):插入数据数据2成功,主线程:disposeRequestThread-1
  30. ****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变量,代码如下:

  1. ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

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

InheritableThreadLocal常用的方法也有3个:

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

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

  1. package com.itsoku.chat24;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.LinkedBlockingDeque;
  6. import java.util.concurrent.ThreadPoolExecutor;
  7. import java.util.concurrent.TimeUnit;
  8. import java.util.concurrent.atomic.AtomicInteger;
  9. /**
  10. * 跟着阿里p7学并发,微信公众号:javacode2018
  11. */
  12. public class Demo4 {
  13. //创建一个操作Thread中存放请求任务追踪id口袋的对象,子线程可以继承父线程中内容
  14. static InheritableThreadLocal<String> traceIdKD = new InheritableThreadLocal<>();
  15. static AtomicInteger threadIndex = new AtomicInteger(1);
  16. //创建处理请求的线程池子
  17. static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
  18. 3,
  19. 60,
  20. TimeUnit.SECONDS,
  21. new LinkedBlockingDeque<>(),
  22. r -> {
  23. Thread thread = new Thread(r);
  24. thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
  25. return thread;
  26. });
  27. //记录日志
  28. public static void log(String msg) {
  29. StackTraceElement stack[] = (new Throwable()).getStackTrace();
  30. //获取当前线程存放tranceId口袋中的内容
  31. String traceId = traceIdKD.get();
  32. System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
  33. }
  34. //模拟controller
  35. public static void controller(List<String> dataList) {
  36. log("接受请求");
  37. service(dataList);
  38. }
  39. //模拟service
  40. public static void service(List<String> dataList) {
  41. log("执行业务");
  42. dao(dataList);
  43. }
  44. //模拟dao
  45. public static void dao(List<String> dataList) {
  46. CountDownLatch countDownLatch = new CountDownLatch(dataList.size());
  47. log("执行数据库操作");
  48. String threadName = Thread.currentThread().getName();
  49. //模拟插入数据
  50. for (String s : dataList) {
  51. new Thread(() -> {
  52. try {
  53. //模拟数据库操作耗时100毫秒
  54. TimeUnit.MILLISECONDS.sleep(100);
  55. log("插入数据" + s + "成功,主线程:" + threadName);
  56. } catch (InterruptedException e) {
  57. e.printStackTrace();
  58. } finally {
  59. countDownLatch.countDown();
  60. }
  61. }).start();
  62. }
  63. //等待上面的dataList处理完毕
  64. try {
  65. countDownLatch.await();
  66. } catch (InterruptedException e) {
  67. e.printStackTrace();
  68. }
  69. }
  70. public static void main(String[] args) {
  71. //需要插入的数据
  72. List<String> dataList = new ArrayList<>();
  73. for (int i = 0; i < 3; i++) {
  74. dataList.add("数据" + i);
  75. }
  76. //模拟5个请求
  77. int requestCount = 5;
  78. for (int i = 0; i < requestCount; i++) {
  79. String traceId = String.valueOf(i);
  80. disposeRequestExecutor.execute(() -> {
  81. //把traceId放入口袋中
  82. traceIdKD.set(traceId);
  83. try {
  84. controller(dataList);
  85. } finally {
  86. //将tranceId从口袋中移除
  87. traceIdKD.remove();
  88. }
  89. });
  90. }
  91. disposeRequestExecutor.shutdown();
  92. }
  93. }

输出:

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

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

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

来源:http://itsoku.com/course/1/24
posted @ 2022-05-04 20:35  程序员小明1024  阅读(39)  评论(0编辑  收藏  举报