java高併發系列第24篇文章。java
環境:jdk1.8。web
咱們仍是以解決問題的方式來引出
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); } //模擬controller public static void controller(List<String> dataList) { log("接受請求"); service(dataList); } //模擬service public static void service(List<String> dataList) { log("執行業務"); dao(dataList); } //模擬dao public 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成功 ****1565338891288,[線程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入數據數據2成功 ****1565338891288,[線程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入數據數據1成功 ****1565338891288,[線程:disposeRequestThread-2],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受請求 ****1565338891288,[線程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入數據數據2成功 ****1565338891288,[線程:disposeRequestThread-2],com.itsoku.chat24.Demo1.service(Demo1.java:42):執行業務 ****1565338891289,[線程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:48):執行數據庫操做 ****1565338891289,[線程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入數據數據0成功 ****1565338891289,[線程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入數據數據1成功 ****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參數,就解決了,代碼以下:高併發
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); } //模擬controller public static void controller(List<String> dataList, String traceId) { log("接受請求", traceId); service(dataList, traceId); } //模擬service public static void service(List<String> dataList, String traceId) { log("執行業務", traceId); dao(dataList, traceId); } //模擬dao public 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成功 ****1565339559775[traceId:0],[線程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入數據數據2成功 ****1565339559775[traceId:3],[線程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入數據數據1成功 ****1565339559775[traceId:2],[線程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入數據數據2成功 ****1565339559775[traceId:3],[線程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入數據數據2成功 ****1565339559775[traceId:4],[線程:disposeRequestThread-1],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受請求 ****1565339559776[traceId:4],[線程:disposeRequestThread-1],com.itsoku.chat24.Demo2.service(Demo2.java:42):執行業務 ****1565339559776[traceId:4],[線程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:48):執行數據庫操做 ****1565339559776[traceId:4],[線程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入數據數據0成功 ****1565339559776[traceId:4],[線程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入數據數據1成功 ****1565339559776[traceId:4],[線程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入數據數據2成功
上面咱們經過修改代碼的方式,把問題解決了,但前提是大家的系統都像上面這麼簡單,功能不多,須要改的代碼不多,能夠這麼去改。但事與願違,咱們的系統通常功能都是比較多的,若是咱們都一個個去改,豈不是要瘋掉,改代碼還涉及到從新測試,風險也不可控。那有什麼好辦法麼?
仍是拿上面的問題,咱們來分析一下,每一個請求都是由一個線程處理的,線程就至關於一我的同樣,每一個請求至關於一個任務,任務來了,人來處理,處理完畢以後,再處理下一個請求任務。人身上是否是有不少口袋,人剛開始準備處理任務的時候,咱們把任務的編號放在處理者的口袋中,而後處理中一路攜帶者,處理過程當中若是須要用到這個編號,直接從口袋中獲取就能夠了。那麼恰好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); } //模擬controller public static void controller(List<String> dataList) { log("接受請求"); service(dataList); } //模擬service public static void service(List<String> dataList) { log("執行業務"); dao(dataList); } //模擬dao public 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)相關聯。」
繼續上面的實例,dao中循環處理dataList的內容,假如dataList處理比較耗時,咱們想加快處理速度有什麼辦法麼?你們已經想到了,用多線程並行處理dataList
,那麼咱們把代碼改一下,以下:
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 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); } //模擬controller public static void controller(List<String> dataList) { log("接受請求"); service(dataList); } //模擬service public static void service(List<String> dataList) { log("執行業務"); dao(dataList); } //模擬dao public 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); } //模擬controller public static void controller(List<String> dataList) { log("接受請求"); service(dataList); } //模擬service public static void service(List<String> dataList) { log("執行業務"); dao(dataList); } //模擬dao public 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
交流,也能夠留言,謝謝。
java高併發系列連載中,總計估計會有四五十篇文章。
阿里p7一塊兒學併發,公衆號:路人甲java,天天獲取最新文章!