1. 程式人生 > >java高併發系列 - 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)

java高併發系列 - 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)

java高併發系列第24篇文章。

環境:jdk1.8。

本文內容

  1. 需要解決的問題
  2. 介紹ThreadLocal
  3. 介紹InheritableThreadLocal

需要解決的問題

我們還是以解決問題的方式來引出ThreadLocalInheritableThreadLocal,這樣印象會深刻一些。

目前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成功

上面我們通過修改程式碼的方式,把問題解決了,但前提是你們的系統都像上面這麼簡單,功能很少,需要改的程式碼很少,可以這麼去改。但事與願違,我們的系統一般功能都是比較多的,如果我們都一個個去改,豈不是要瘋掉,改程式碼還涉及到重新測試,風險也不可控。那有什麼好辦法麼?

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);
    }

    //模擬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)相關聯。”

InheritableThreadLocal

繼續上面的例項,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了,和期望的結果一致。

希望通過這篇文章可以學會使用InheritableThreadLocalInheritableThreadLocal。有問題可以加我微信itsoku交流,也可以留言,謝謝。

java高併發系列目錄

  1. 第1天:必須知道的幾個概念
  2. 第2天:併發級別
  3. 第3天:有關並行的兩個重要定律
  4. 第4天:JMM相關的一些概念
  5. 第5天:深入理解程序和執行緒
  6. 第6天:執行緒的基本操作
  7. 第7天:volatile與Java記憶體模型
  8. 第8天:執行緒組
  9. 第9天:使用者執行緒和守護執行緒
  10. 第10天:執行緒安全和synchronized關鍵字
  11. 第11天:執行緒中斷的幾種方式
  12. 第12天JUC:ReentrantLock重入鎖
  13. 第13天:JUC中的Condition物件
  14. 第14天:JUC中的LockSupport工具類,必備技能
  15. 第15天:JUC中的Semaphore(訊號量)
  16. 第16天:JUC中等待多執行緒完成的工具類CountDownLatch,必備技能
  17. 第17天:JUC中的迴圈柵欄CyclicBarrier的6種使用場景
  18. 第18天:JAVA執行緒池,這一篇就夠了
  19. 第19天:JUC中的Executor框架詳解1
  20. 第20天:JUC中的Executor框架詳解2
  21. 第21天:java中的CAS,你需要知道的東西
  22. 第22天:JUC底層工具類Unsafe,高手必須要了解
  23. 第23天:JUC中原子類,一篇就夠了

java高併發系列連載中,總計估計會有四五十篇文章。

阿里p7一起學併發,公眾號:路人甲java,每天獲取最新文章!

相關推薦

java併發系列 - 24ThreadLocalInheritableThreadLocal通俗易懂

java高併發系列第24篇文章。 環境:jdk1.8。 本文內容 需要解決的問題 介紹ThreadLocal 介紹InheritableThreadLocal 需要解決的問題 我們還是以解決問題的方式來引出ThreadLocal、InheritableThreadLocal,這樣印象會深刻一些。 目前

java併發系列 - 14JUC中的LockSupport工具類,必備技能

這是java高併發系列第14篇文章。 本文主要內容: 講解3種讓執行緒等待和喚醒的方法,每種方法配合具體的示例 介紹LockSupport主要用法 對比3種方式,瞭解他們之間的區別 LockSupport位於java.util.concurrent(簡稱juc)包中,算是juc中一個基礎類,juc中很多地

java併發系列 - 15JUC中的Semaphore,最簡單的限流工具類,必備技能

這是java高併發系列第15篇文章 Semaphore(訊號量)為多執行緒協作提供了更為強大的控制方法,前面的文章中我們學了synchronized和重入鎖ReentrantLock,這2種鎖一次都只能允許一個執行緒訪問一個資源,而訊號量可以控制有多少個執行緒可以同時訪問特定的資源。 Semaphore常用

java併發系列 - 16JUC中等待多執行緒完成的工具類CountDownLatch,必備技能

這是java高併發系列第16篇文章。 本篇內容 介紹CountDownLatch及使用場景 提供幾個示例介紹CountDownLatch的使用 手寫一個並行處理任務的工具類 假如有這樣一個需求,當我們需要解析一個Excel裡多個sheet的資料時,可以考慮使用多執行緒,每個執行緒解析一個sheet裡的資料

java併發系列 - 17JUC中的迴圈柵欄CyclicBarrier常見的6種使用場景及程式碼示例

這是java高併發系列第17篇。 本文主要內容: 介紹CyclicBarrier 6個示例介紹CyclicBarrier的使用 對比CyclicBarrier和CountDownLatch CyclicBarrier簡介 CyclicBarrier通常稱為迴圈屏障。它和CountDownLatch很相似,

java併發系列 - 21java中的CAS操作,java併發的基石

這是java高併發系列第21篇文章。 本文主要內容 從網站計數器實現中一步步引出CAS操作 介紹java中的CAS及CAS可能存在的問題 悲觀鎖和樂觀鎖的一些介紹及資料庫樂觀鎖的一個常見示例 使用java中的原子操作實現網站計數器功能 我們需要解決的問題 需求:我們開發了一個網站,需要對訪問量進行統計,使

java併發系列 - 22java中底層工具類Unsafe,高手必須要了解

這是java高併發系列第22篇文章,文章基於jdk1.8環境。 本文主要內容 基本介紹 通過反射獲取Unsafe例項 Unsafe中的CAS操作 Unsafe中原子操作相關方法介紹 Unsafe中執行緒排程相關方法 park和unpark示例 Unsafe鎖示例 Unsafe中保證變數的可見性 Unsafe

java併發系列 - 23JUC中原子類,一篇就夠了

這是java高併發系列第23篇文章,環境:jdk1.8。 本文主要內容 JUC中的原子類介紹 介紹基本型別原子類 介紹陣列型別原子類 介紹引用型別原子類 介紹物件屬性修改相關原子類 預備知識 JUC中的原子類都是都是依靠volatile、CAS、Unsafe類配合來實現的,需要了解的請移步: volati

java併發系列 - 25掌握JUC中的阻塞佇列

這是java高併發系列第25篇文章。 環境:jdk1.8。 本文內容 掌握Queue、BlockingQueue介面中常用的方法 介紹6中阻塞佇列,及相關場景示例 重點掌握4種常用的阻塞佇列 Queue介面 佇列是一種先進先出(FIFO)的資料結構,java中用Queue介面來表示佇列。 Queue介面中

java併發系列 - 27實戰篇,介面效能成倍提升,讓同事刮目相看,現學現用

這是java高併發系列第27篇文章。 開發環境:jdk1.8。 案例講解 電商app都有用過吧,商品詳情頁,需要給他們提供一個介面獲取商品相關資訊: 商品基本資訊(名稱、價格、庫存、會員價格等) 商品圖片列表 商品描述資訊(描述資訊一般是由富文字編輯的大文字資訊) 資料庫中我們用了3張表儲存上面的資訊:

java併發系列 - 31獲取執行緒執行結果,這6種方法你都知道?

這是java高併發系列第31篇。 環境:jdk1.8。 java高併發系列已經學了不少東西了,本篇文章,我們用前面學的知識來實現一個需求: 在一個執行緒中需要獲取其他執行緒的執行結果,能想到幾種方式?各有什麼優缺點? 結合這個需求,我們使用6種方式,來對之前學過的知識點做一個回顧,加深記憶。 方式1:Thre

java併發系列 - 32併發中計數器的實現方式有哪些?

這是java高併發系列第32篇文章。 java環境:jdk1.8。 本文主要內容 4種方式實現計數器功能,對比其效能 介紹LongAdder 介紹LongAccumulator 需求:一個jvm中實現一個計數器功能,需保證多執行緒情況下資料正確性。 我們來模擬50個執行緒,每個執行緒對計數器遞增100萬次

java併發系列-1:必須知道的幾個概念

java高併發系列-第1天:必須知道的幾個概念 同步(Synchronous)和非同步(Asynchronous) 同步和非同步通常來形容一次方法呼叫,同步方法呼叫一旦開始,呼叫者必須等到方法呼叫返回後,才能繼續後續的行為。非同步方法呼叫更像一個訊息傳遞,一旦開始,方法呼叫就會立即返回,呼叫者就可以繼續後續的

java併發系列 - 6:執行緒的基本操作

新建執行緒 新建執行緒很簡單。只需要使用new關鍵字建立一個執行緒物件,然後呼叫它的start()啟動執行緒即可。 Thread thread1 = new Thread1(); t1.start(); 那麼執行緒start()之後,會幹什麼呢?執行緒有個run()方法,start()會建立一個新的執行緒並讓

java併發系列 - 12JUC:ReentrantLock重入鎖

java高併發系列 - 第12天JUC:ReentrantLock重入鎖 本篇文章開始將juc中常用的一些類,估計會有十來篇。 synchronized的侷限性 synchronized是java內建的關鍵字,它提供了一種獨佔的加鎖方式。synchronized的獲取和釋放鎖由jvm實現,使用者不需要顯示的釋

java併發系列 - 29併發中常見的限流方式

這是java高併發系列第29篇。 環境:jdk1.8。 本文內容 介紹常見的限流演算法 通過控制最大併發數來進行限流 通過漏桶演算法來進行限流 通過令牌桶演算法來進行限流 限流工具類RateLimiter 常見的限流的場景 秒殺活動,數量有限,訪問量巨大,為了防止系統宕機,需要做限流處理 國慶期間,一般

【譯】你不知道的Chrome除錯工具技巧 the Elements panel元素面板

特別宣告 本文是作者 Tomek Sułkowski 釋出在 medium 上的一個系列。據作者透露一共有24篇,一直更新到12月24日 版權歸原作者所有。 前兩篇的翻譯連結我已經給到了作者本人,雖然他不理解中文,但是他還是很開心哈哈,截圖在最後 譯者在翻譯前已經和作者溝通得到了翻譯整個系列的許可。 為

江西偉人系列三篇唐宋八大家歐陽修

青春 logs body 指導 文學家 分享 itl 1年 html 歐陽修(1007年8月1日-1072年9月22日),字永叔,號醉翁,晚號六一居士, 漢族,吉州永豐(今江西省吉安市永豐縣)人,北宋政治家、文學家,且在政治上負有盛名。因吉州原屬廬陵郡,以“

24Django實戰24講師列表頁

try object imp 圖片 light except div 分頁 integer 1、復制teracher-list.html到templates目錄下 2、編輯teacher-list.html,繼承base模板 3、編輯organization.vie

Java基礎複習繼承過載this與super多型static

一 繼承 1.繼承的由來 多個類中有相同成員變數和成員方法,那麼我們會把這些共性抽取到另外一個類中, 這時多個類只需要繼承剛剛抽取出來這個類即可 2.父類 和 子類 一個類A 繼承了 類B,那麼 類B稱為父類/超類/基類(superclass) 類