Java ThreadLocal Variables – When and How to Use?

如今,并發(fā)應(yīng)用程序最關(guān)鍵的方面之一就是共享數(shù)據(jù)。 當(dāng)我們創(chuàng)建實現(xiàn)Runnable接口的線程,然后使用同一Runnable對象啟動各種Thread對象時,所有線程都共享在runnable對象內(nèi)部定義的相同屬性。

這實質(zhì)上意味著,如果我們更改線程中的任何屬性,則所有線程都將受到此更改的影響,并且將在第一個線程中看到修改后的值。 有時這是期望的行為,例如 多個線程增加/減少相同的計數(shù)器變量;

但有時我們要確保每個線程必須在其自己的線程實例副本上工作,并且不影響其他數(shù)據(jù),這時ThreadLocal將是一個不錯的選擇。

什么時候我們需要使用ThreadLocal?

例如,您正在開發(fā)某電商網(wǎng)站交易系統(tǒng)。您需要為每個請求此控制器流程的每個客戶生成唯一的交易ID,并且需要將此交易ID傳遞到manager / DAO類中的業(yè)務(wù)方法以進(jìn)行記錄。一種解決方案是將該事務(wù)ID作為參數(shù)傳遞給所有業(yè)務(wù)方法。但這不是一個好的解決方案,因為代碼是多余且不必要的。

為了解決這個問題,您可以在這里使用ThreadLocal變量。您可以在控制器或任何預(yù)處理器中生成事務(wù)ID。并在ThreadLocal中設(shè)置該交易ID。此后,無論此控制器調(diào)用什么方法,它們都可以從threadlocal訪問此事務(wù)ID。還要注意,應(yīng)用程序控制器一次將處理一個以上的請求,并且由于每個請求都是在框架級別在單獨的線程中處理的,因此事務(wù)ID對于每個線程都是唯一的,并且可以從該線程的整個執(zhí)行路徑進(jìn)行訪問。

ThreadLocal class

Java Concurrency API使用ThreadLocal類為性能良好的線程局部變量提供了一種干凈的機制。

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

此類提供線程局部變量。 這些變量與普通變量不同,因為每個訪問線程(通過其get或set方法)的線程都有其自己的,獨立初始化的變量副本。

ThreadLocal實例通常是希望將狀態(tài)與線程關(guān)聯(lián)的類中的私有靜態(tài)字段(例如,用戶ID或交易ID)。

此類具有以下方法:

  • get():返回此線程局部變量在當(dāng)前線程副本中的值。
  • initialValue():為此線程局部變量返回當(dāng)前線程的“初始值”。
  • remove():為此線程局部變量刪除當(dāng)前線程的值。
  • set(T value):將此線程局部變量的當(dāng)前線程副本設(shè)置為指定值。

如何使用ThreadLocal?

下面的示例使用兩個線程局部變量,即threadId和startDate。 根據(jù)建議,這兩個字段均被定義為“私有靜態(tài)”字段。 “ threadId”將用于標(biāo)識當(dāng)前正在運行的線程,“ startDate”將用于獲取線程開始執(zhí)行的時間。 以上信息將打印在控制臺中,以驗證每個線程是否維護(hù)了自己的變量副本。


/**
 * @author 王琦 <QQ.Email>1124602935@qq.com</QQ.Email>
 * @date 19/12/3 中午12:50
 * @description
 */
public class DemoTask implements Runnable {

    private static final AtomicInteger nextId = new AtomicInteger();

    private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){

        @Override
        protected Integer initialValue() {
            return nextId.getAndIncrement();
        }
    };

    private static final ThreadLocal<String> startDate = new ThreadLocal<String>(){

        @Override
        protected String initialValue() {
            return dateFormat.format(System.currentTimeMillis());
        }
    };

    // Returns the current thread's unique ID
    public int getThreadId(){
        return threadId.get();
    }

    @Override
    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 (Exception e){
            e.printStackTrace();
        }
        System.out.printf("Thread Finished: %s : %s\n\n", getThreadId(), startDate.get());
    }


    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new DemoTask());
        thread1.start();
        thread1.join();

        Thread thread2 = new Thread(new DemoTask());
        thread2.start();
        thread2.join();

        Thread thread3 = new Thread(new DemoTask());
        thread3.start();
        thread3.join();
    }
}

現(xiàn)在,要驗證變量本質(zhì)上是否能夠維持其狀態(tài),而與多個線程的多次初始化無關(guān),讓我們創(chuàng)建此任務(wù)的三個實例。 啟動線程; 然后驗證他們在控制臺中打印的信息。

public static void main(String[] args) throws InterruptedException {
        new Thread(new DemoTask()).start();
        Thread.sleep(1000);
        new Thread(new DemoTask()).start();
        Thread.sleep(1000);
        new Thread(new DemoTask()).start();
    }

控制臺輸出:

Starting Thread: 0 : 2019-12-03 15:09:29
Starting Thread: 1 : 2019-12-03 15:09:30
Starting Thread: 2 : 2019-12-03 15:09:31
Thread Finished: 0 : 2019-12-03 15:09:29

Thread Finished: 1 : 2019-12-03 15:09:30

Thread Finished: 2 : 2019-12-03 15:09:31

為了我們可以清楚地識別出線程本地值對于每個線程實例都是安全的,調(diào)整下打印結(jié)果的順序:

Starting Thread: 0 : 2019-12-03 15:09:29
Thread Finished: 0 : 2019-12-03 15:09:29

Starting Thread: 1 : 2019-12-03 15:09:30
Thread Finished: 1 : 2019-12-03 15:09:30

Starting Thread: 2 : 2019-12-03 15:09:31
Thread Finished: 2 : 2019-12-03 15:09:31

結(jié)論

局部線程的最常見用法是當(dāng)您擁有一些不是線程安全的對象,但又想避免使用同步的關(guān)鍵字/塊來同步對該對象的訪問時。 而是給每個線程使用其自己的對象實例。
同步或threadlocal的一個很好的選擇是使變量成為局部變量。 局部變量始終是線程安全的。 唯一可能阻止您執(zhí)行此操作的是應(yīng)用程序設(shè)計約束。

謹(jǐn)慎使用

在wabapp服務(wù)器中,它可能保留一個線程池,因此應(yīng)在響應(yīng)客戶端之前刪除ThreadLocal var,因為當(dāng)前線程可能被下一個請求重用。 另外,如果您在完成操作后不進(jìn)行清理,則它對作為已部署的Webapp的一部分加載的類的任何引用都將保留在永久堆中,并且永遠(yuǎn)不會收集垃圾。

原文鏈接:RelaxHeart網(wǎng) / Tec博客 / 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)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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