當前位置: 妍妍網 > 碼農

【和八股文說再見】詳解Java中ThreadLocal使用方法

2024-07-15碼農


大家好!我是老碼農。

後面跟大家陸續分享作為一個後端應該掌握的各種技能。

文章內容: 原理+詳細範例說明,便於理解。

好啦!廢話不多說,開始分享。

詳解Java中ThreadLocal使用方法

ThreadLocal 是 Java 中的一個類,它提供了執行緒局部變量的支持。簡單來說,ThreadLocal 允許你建立的變量在每個執行緒中都有其獨立的副本,各個執行緒之間互不影響,這樣每個執行緒都可以獨立地改變自己的副本,而不會影響其他執行緒的副本。

ThreadLocal 的工作原理

  1. 每個執行緒持有一個 ThreadLocalMap:

  • 每個執行緒內部都有一個私有的 ThreadLocalMap,它是一個 Entry 陣列,用於儲存 ThreadLocal 物件及其對應的值。ThreadLocalMap 並不是直接使用陣列,而是使用了類似開放尋址法的線性探測法來處理哈希沖突。

  • ThreadLocal 物件作為鍵:

  • ThreadLocal 是一個泛型類,用作執行緒局部變量的鍵。每個 ThreadLocal 例項在不同的執行緒中可以對應不同的值。

  • 獲取和設定值的過程:

  • 當呼叫 ThreadLocal 的 get() 方法時,實際上是透過當前執行緒的 ThreadLocalMap 獲取當前 ThreadLocal 物件的值。

  • 當呼叫 ThreadLocal 的 set(value) 方法時,實際上是透過當前執行緒的 ThreadLocalMap 將當前 ThreadLocal 物件與指定的值關聯起來。

  • ThreadLocalMap 使用 ThreadLocal 的弱參照作為鍵,這樣即使 ThreadLocal 物件被回收,也不會導致記憶體泄漏,但是如果 ThreadLocal 沒有被及時回收,仍然會導致記憶體泄漏。

  • 記憶體泄漏問題:

  • 如果使用 ThreadLocal 的執行緒長時間存在,並且沒有手動呼叫 remove() 方法刪除 ThreadLocal 物件,那麽當前執行緒持有的 ThreadLocalMap 中的 Entry 物件可能會長時間存在,從而導致記憶體泄漏。

  • ThreadLocal 的實作方式:

  • ThreadLocal 是透過繼承 ThreadLocal 類並重寫 initialValue() 方法或者透過匿名內部類的方式建立,來定義每個執行緒獨立的變量。每個 ThreadLocal 物件實際上是在當前執行緒的 ThreadLocalMap 中儲存的一個 Entry 物件。

  • 使用場景

  • 執行緒安全的物件封裝: 當某個物件需要在多個方法中使用,但每個方法需要獨立維護物件的副本時,可以使用 ThreadLocal 來封裝這個物件,每個執行緒擁有自己獨立的物件副本,避免了執行緒安全問題。

  • 避免傳遞參數: 在一些復雜的方法呼叫鏈中,如果需要傳遞大量的參數,可以使用 ThreadLocal 將這些參數封裝起來,避免手動傳遞。

  • 資料庫連線管理: 典型的套用是在使用資料庫連線池時,透過 ThreadLocal 來管理資料庫連線,確保每個執行緒使用自己的資料庫連線,避免多執行緒並行存取時的數據混亂問題。

  • 總結來說,ThreadLocal 是一種實作執行緒局部變量的機制,透過在每個執行緒內部維護一個獨立的變量副本,可以實作多執行緒並行存取時的執行緒安全。

    當使用 ThreadLocal 時,通常會定義一個靜態的 ThreadLocal 變量,並透過其 initialValue() 方法或者在需要時設定初始值。讓我們透過一個簡單的範例來說明如何使用 ThreadLocal,並理解其工作原理。

    假設我們有一個需求:在多個執行緒中,每個執行緒需要獨立地保存一個計數器,並且能夠在每次呼叫時增加這個計數器的值。

    範例程式碼

    首先,我們定義一個 ThreadLocal 變量 counter ,並重寫其 initialValue() 方法來設定初始值:

    import java.util.concurrent.atomic.AtomicInteger;
    public classThreadLocalExample{
    // 定義一個 ThreadLocal 變量
    privatestatic ThreadLocal<AtomicInteger> counter = ThreadLocal.withInitial(() -> new AtomicInteger(0));
    // 定義一個方法,每次呼叫增加計數器的值
    publicstaticvoidincrementCounter(){
    // 獲取當前執行緒的計數器物件
    AtomicInteger currentValue = counter.get();
    // 對計數器值加一
    currentValue.incrementAndGet();
    }
    // 定義一個方法,獲取當前執行緒的計數器值
    publicstaticintgetCounter(){
    return counter.get().get();
    }
    // 主函式,演示多執行緒環境下的使用
    publicstaticvoidmain(String[] args)throws InterruptedException {
    // 建立多個執行緒來呼叫 incrementCounter 方法
    Thread[] threads = new Thread[5];
    for (int i = 0; i < threads.length; i++) {
    threads[i] = new Thread(() -> {
    for (int j = 0; j < 10; j++) {
    incrementCounter();
    System.out.println(Thread.currentThread().getName() + ": Counter = " + getCounter());
    }
    });
    threads[i].start();
    }
    // 等待所有執行緒執行完成
    for (Thread thread : threads) {
    thread.join();
    }
    }
    }



    範例解釋

    1. 定義 ThreadLocal 變量:

  • 我們定義了一個 counter 變量,它是一個 ThreadLocal<AtomicInteger> 。這意味著每個執行緒都可以透過 counter.get() 方法獲取到屬於自己的 AtomicInteger 物件,而不會影響其他執行緒的計數器。

  • 初始化設定:

  • 使用 ThreadLocal.withInitial(() -> new AtomicInteger(0)) ,我們定義了 counter 的初始值為 new AtomicInteger(0) 。這意味著每個執行緒第一次呼叫 get() 方法時,會為該執行緒建立一個新的 AtomicInteger 物件。

  • 增加計數器值:

  • incrementCounter() 方法透過 counter.get() 獲取當前執行緒的計數器物件,然後對其進行遞增操作。

  • 多執行緒使用:

  • main 方法中,我們建立了5個執行緒來並行地呼叫 incrementCounter() 方法,每個執行緒執行10次。每次執行時,打印出當前執行緒的名稱和計數器的值,以驗證每個執行緒的計數器都是獨立增加的。

  • 結果輸出:

  • 透過觀察輸出結果,可以看到每個執行緒都有自己獨立的計數器,並且能夠獨立地進行增加操作,而不會影響其他執行緒的計數器值。

  • 結論

    ThreadLocal 使得每個執行緒可以在其內部擁有一個獨立的變量副本,這在多執行緒編程中特別有用,能夠避免使用 synchronized 或者額外的參數傳遞來實作執行緒安全。記住,使用 ThreadLocal 時要特別註意在不再需要時及時呼叫 remove() 方法,以防止記憶體泄漏問題。

    總結

    好啦!今天就分享到這裏。

    記得「 關註 」、點「 」、點「 在看 」支持一下老碼農,感謝大家的支持!