Java ThreadLocal變量 - 什么時(shí)候用以及如何使用?

今天,并發(fā)應(yīng)用程序最關(guān)鍵的方面之一是共享數(shù)據(jù)。當(dāng)您創(chuàng)建實(shí)現(xiàn)Runnable接口的線(xiàn)程,然后使用相同的Runnable對(duì)象啟動(dòng)各種線(xiàn)程對(duì)象時(shí),所有線(xiàn)程共享Runnable對(duì)象內(nèi)定義的相同屬性。這實(shí)際上意味著,如果您更改線(xiàn)程中的任何屬性,所有線(xiàn)程都將受到此更改的影響,并將在第一個(gè)線(xiàn)程中看到修改后的值。有時(shí)它是期望的行為,例如多個(gè)線(xiàn)程增加/減少相同的計(jì)數(shù)器變量;但有時(shí)您希望確保每個(gè)線(xiàn)程必須在它自己的線(xiàn)程實(shí)例副本上工作,而不影響其他數(shù)據(jù)。

什么時(shí)候使用ThreadLocal?

例如,假設(shè)您正在處理一個(gè)電子商務(wù)應(yīng)用程序。您需要為每個(gè)請(qǐng)求此控制器流程的客戶(hù)生成唯一的事務(wù)id,并且需要將此事務(wù)id傳遞給manager/DAO類(lèi)中的業(yè)務(wù)方法,以便進(jìn)行日志記錄。一個(gè)解決方案是將這個(gè)事務(wù)id作為參數(shù)傳遞給所有業(yè)務(wù)方法。但這不是一個(gè)好的解決方案,因?yàn)榇a會(huì)產(chǎn)生不必要的冗余。

想要解決這個(gè)問(wèn)題,可以使用ThreadLocal變量。您可以在控制器或任何預(yù)處理器攔截器中生成事務(wù)id;并在ThreadLocal中設(shè)置此事務(wù)id。之后,無(wú)論控制器調(diào)用什么方法,它們都可以從threadlocal訪問(wèn)這個(gè)事務(wù)id。還請(qǐng)注意,應(yīng)用程序控制器將同時(shí)處理多個(gè)請(qǐng)求,由于每個(gè)請(qǐng)求在框架級(jí)別上在單獨(dú)的線(xiàn)程中處理,因此事務(wù)id對(duì)于每個(gè)線(xiàn)程都是惟一的,并且可以從線(xiàn)程的所有執(zhí)行路徑訪問(wèn)它。

更多信息:與JAX-RS ResteasyProviderFactory共享上下文數(shù)據(jù)(ThreadLocalStack示例)

ThreadLocal類(lèi)內(nèi)部?

Java并發(fā)API使用ThreadLocal類(lèi)為線(xiàn)程本地變量提供了一種干凈的機(jī)制,具有非常好的性能。

public class ThreadLocal<T> extends Object {...}

該類(lèi)提供線(xiàn)程本地變量。這些變量與普通的對(duì)應(yīng)變量的不同之處在于,每個(gè)訪問(wèn)一個(gè)變量的線(xiàn)程(通過(guò)其get或set方法)都有自己的、獨(dú)立初始化的變量副本。ThreadLocal實(shí)例通常是類(lèi)中的私有靜態(tài)字段,類(lèi)希望將狀態(tài)與線(xiàn)程關(guān)聯(lián)(例如,用戶(hù)ID或事務(wù)ID)。

這個(gè)類(lèi)有以下方法:

  1. get():返回該線(xiàn)程本地變量的當(dāng)前線(xiàn)程副本中的值。
  2. initialValue():返回這個(gè)線(xiàn)程本地變量的當(dāng)前線(xiàn)程的“初始值”。
  3. remove():刪除這個(gè)線(xiàn)程本地變量的當(dāng)前線(xiàn)程值。
  4. set(T value):將當(dāng)前線(xiàn)程的這個(gè)線(xiàn)程本地變量的副本設(shè)置為指定的值。

如何使用ThreadLocal?

下面的例子使用了兩個(gè)線(xiàn)程本地變量,即threadIdstartDate。兩者都被定義為推薦的private static字段。threadId將用于標(biāo)識(shí)當(dāng)前正在運(yùn)行的線(xiàn)程,startDate將用于獲取線(xiàn)程開(kāi)始執(zhí)行的時(shí)間。以上信息將打印在控制臺(tái)中,以驗(yàn)證每個(gè)線(xiàn)程都維護(hù)了自己的變量副本。

public class DemoTask implements Runnable {
    // 包含要分配的下一個(gè)線(xiàn)程ID的原子整數(shù)
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // 包含每個(gè)線(xiàn)程的ID的ThreadLocal變量
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return nextId.getAndIncrement();// 獲取值,并遞增
        }
    };

    // 返回當(dāng)前線(xiàn)程的唯一ID,必要時(shí)為其賦值
    public int getThreadId() {
        return threadId.get();
    }

    // 返回當(dāng)前線(xiàn)程的啟動(dòng)時(shí)間戳
    private static final ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
        protected Date initialValue() {
            return new Date();
        }
    };


    public void run() {
        System.out.printf("Starting Thread: %s : %s\n", getThreadId(), startDate.get());
        try {
            TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Thread Finished: %s : %s\n", getThreadId(), startDate.get());
    }
}

現(xiàn)在,為了驗(yàn)證變量基本上能夠維護(hù)它們的狀態(tài),而不需要考慮多個(gè)線(xiàn)程的多個(gè)初始化,讓我們創(chuàng)建這個(gè)任務(wù)的三個(gè)實(shí)例;啟動(dòng)線(xiàn)程;然后驗(yàn)證它們?cè)诳刂婆_(tái)打印的信息。

public class ThreadLocalExample {
    public static void main(String[] args) throws Exception {
        Thread thread[] = new Thread[3];
        for (int i = 0; i < 3; i++) {
            DemoTask demoTask = new DemoTask();
            thread[i] = new Thread(demoTask, "Thread " + i);
        }
        for (int i = 0; i < 3; i++) {
            thread[i].start();
            TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
        }
    }
}

輸出結(jié)果為

Starting Thread: 0 : Mon Mar 04 00:15:28 CST 2019
Thread Finished: 0 : Mon Mar 04 00:15:28 CST 2019

Starting Thread: 1 : Mon Mar 04 00:15:31 CST 2019
Thread Finished: 1 : Mon Mar 04 00:15:31 CST 2019

Starting Thread: 2 : Mon Mar 04 00:15:37 CST 2019
Thread Finished: 2 : Mon Mar 04 00:15:37 CST 2019

在上面的輸出中,每次打印語(yǔ)句的順序都會(huì)有所不同。我將它們按順序排列,這樣我們就可以清楚地確定每個(gè)線(xiàn)程實(shí)例的線(xiàn)程本地值都是安全的;不會(huì)混雜在一起。自己嘗試一下。

ThreadLocal最常見(jiàn)的用法是,當(dāng)您有某個(gè)對(duì)象不是線(xiàn)程安全的,但是您希望避免使用synchronized關(guān)鍵字/塊對(duì)該對(duì)象進(jìn)行同步訪問(wèn)時(shí)。相反,應(yīng)該為每個(gè)線(xiàn)程提供其要使用的對(duì)象的實(shí)例。
同步或threadlocal的一個(gè)很好的替代方法是將變量變成一個(gè)本地變量。局部變量總是線(xiàn)程安全的。唯一可能阻止您這樣做的是您的應(yīng)用程序設(shè)計(jì)約束。

在wabapp服務(wù)器中,它可能保留一個(gè)線(xiàn)程池,因此在響應(yīng)客戶(hù)機(jī)之前應(yīng)該刪除ThreadLocal變量,因?yàn)楫?dāng)前線(xiàn)程可能被下一個(gè)請(qǐng)求重用。此外,如果在完成時(shí)不清理,那么它對(duì)作為已部署webapp的一部分加載的類(lèi)的任何引用都將保留在永久堆中,永遠(yuǎn)不會(huì)被垃圾收集。

參考資料
Java ThreadLocal Variables – When and How to Use?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容