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

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

什么時候使用ThreadLocal?

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

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

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

ThreadLocal類內(nèi)部?

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

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

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

這個類有以下方法:

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

如何使用ThreadLocal?

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

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

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

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

    // 返回當(dāng)前線程的啟動時間戳
    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)在,為了驗證變量基本上能夠維護(hù)它們的狀態(tài),而不需要考慮多個線程的多個初始化,讓我們創(chuàng)建這個任務(wù)的三個實(shí)例;啟動線程;然后驗證它們在控制臺打印的信息。

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

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

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

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

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

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

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

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