亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么理解ThreadLocal的實現機制

發布時間:2021-11-15 15:50:46 來源:億速云 閱讀:134 作者:iii 欄目:大數據

本篇內容主要講解“怎么理解ThreadLocal的實現機制”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么理解ThreadLocal的實現機制”吧!

下面的例子演示了 ThreadLocal 的典型應用場景。在 jdk 1.8 之前,如果我們希望對日期和時間進行格式化操作,則需要使用 SimpleDateFormat 類,而我們知道它是是線程不安全的,在多線程并發執行時會出現一些奇怪的問題。對于該類使用的最佳實踐則是采用 ThreadLocal 進行包裝,以保證每個線程都有一份屬于自己的 SimpleDateFormat 對象,如下所示:

ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

實現機制

那么 ThreadLocal 是怎么做到讓修飾的對象能夠在每個線程中各自持有一份呢?我們先來從整體的角度簡單概括一下。

在 ThreadLocal 中定義了一個靜態內部類 ThreadLocalMap,可以將其理解為一個特有的 Map 類型,而在 Thread 類中聲明了一個 ThreadLocalMap 類型的 threadLocals 屬性。針對每個 Thread 對象,也就是每個線程來說都包含了一個 ThreadLocalMap 對象,即每個線程都有一個屬于自己的內存數據庫,而數據庫中存儲的就是我們用 ThreadLocal 修飾的對象。整個過程還是有點繞的,可以借助下面這幅圖進行理解:

怎么理解ThreadLocal的實現機制

這里的 key 就是對應的 ThreadLocal 對象自身,而 value 就是 ThreadLocal 修飾的屬性值。當希望獲取該對象時,我們首先需要拿到當前線程對應的 Thread 對象,然后獲取到該對象對應的 threadLocals 屬性,也就拿到了線程私有的內存數據庫,最后以 ThreadLocal 對象為 key 獲取到其修飾的目標值。

線程內存數據庫

接下來看一下相應的源碼實現,首先來看一下內部定義的 ThreadLocalMap 靜態內部類:

static class ThreadLocalMap {

    // 弱引用的key,繼承自 WeakReference
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** ThreadLocal 修飾的對象 */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    /** 初始化大小,必須是二次冪 */
    private static final int INITIAL_CAPACITY = 16;
    /** 承載鍵值對的表,長度必須是二次冪 */
    private Entry[] table;
    /** 記錄鍵值對表的大小 */
    private int size = 0;
    /** 再散列閾值 */
    private int threshold; // Default to 0

    // 構造方法
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

    // 構造方法
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];

        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if (e != null) {
                @SuppressWarnings("unchecked")
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if (key != null) {
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }

    // 省略相應的方法實現
}

ThreadLocalMap 是一個定制化的 Map 實現,可以簡單將其理解為一般的 Map,用作鍵值存儲的內存數據庫,至于為什么要專門實現而不是復用已有的 HashMap,我們在后面進行說明。

API 實現分析

了解了 ThreadLocalMap 的定義,我們再來看一下 ThreadLocal 的實現。對于 ThreadLocal 來說,對外暴露的方法主要有 get、set,以及 remove 三個,下面逐一展開分析。

獲取線程私有值

與一般的 Map 取值操作不同,這里的 ThreadLocal#get 方法并沒有要求提供查詢的 key,也正如前面所說的,這里的 key 就是調用 ThreadLocal#get 方法的 ThreadLocal 對象自身:

public T get() {
    // 獲取當前線程對象
    Thread t = Thread.currentThread();
    // 獲取當前線程對象的 threadLocals 屬性
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 以 ThreadLocal 對象為 key 獲取目標線程私有值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

如果當前線程對應的內存數據庫 map 對象還未創建,則會調用 ThreadLocal#setInitialValue 方法執行創建,如果在構造 ThreadLocal 對象時覆蓋實現了 ThreadLocal#initialValue 方法,則會調用該方法獲取構造的初始化值并記錄到創建的 map 對象中:

private T setInitialValue() {
    // 調用模板方法 initialValue 獲取指定的初始值
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 以當前 ThreadLocal 對象為 key 記錄初始值
        map.set(this, value);
    else
        // 創建 map 并記錄初始值
        createMap(t, value);
    return value;
}
設置線程私有值

再來看一下 ThreadLocal#set 方法,因為 key 就是當前 ThreadLocal 對象,所以 ThreadLocal#set 方法也不需要指定 key:

public void set(T value) {
    // 獲取當前線程對象
    Thread t = Thread.currentThread();
    // 獲取當前線程對象的 threadLocals 屬性
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 以當前 ThreadLocal 對象為 key 記錄線程私有值
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal#get 方法的流程大致一樣,都是操作當前線程私有的內存數據庫 ThreadLocalMap,并記錄目標值。

刪除線程私有值

方法 ThreadLocal#remove 以當前 ThreadLocal 對象為 key,從當前線程內存數據庫 ThreadLocalMap 中刪除目標值,具體邏輯比較簡單:

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        // 以當前 ThreadLocal 對象為 key
        m.remove(this);
}

ThreadLocal 對外暴露的功能雖然有點小神奇,但是具體對應到內部實現并沒有什么復雜的邏輯。如果我們把每個線程持有的專屬 ThreadLocalMap 對象理解為當前線程的私有數據庫,那么也就不難理解 ThreadLocal 的運行機制。每個線程自己維護自己的數據,彼此相互隔離,不存在競爭,也就沒有線程安全問題可言。

真的就高枕無憂了嗎

雖然對于每個線程來說數據是隔離的,但這也不表示任何對象丟到 ThreadLocal 中就萬事大吉了,思考一下下面幾種情況:

  1. 如果記錄在 ThreadLocal 中的是一個線程共享的外部對象呢?

  2. 引入線程池,情況又會有什么變化?

  3. 如果 ThreadLocal 被 static 關鍵字修飾呢?

先來看 第 1 個問題,如果我們記錄的是一個外部線程共享的對象,雖然我們以當前線程私有的 ThreadLocal 對象作為 key 對其進行了存儲,但是惡魔終究是惡魔,共享的本質并不會因此而改變,這種情況下的訪問還是需要進行同步控制,最好的方法就是從源頭屏蔽掉這類問題。我們來舉個例子:

public class ThreadLocalWithSharedInstance implements Runnable {

    // list 是一個事實共享的實例,即使被 ThreadLocal 修飾
    private static List<String> list = new ArrayList<>();
    private ThreadLocal<List<String>> threadLocal = ThreadLocal.withInitial(() -> list);

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            List<String> li = threadLocal.get();
            li.add(Thread.currentThread().getName() + "_" + RandomUtils.nextInt(0, 10));
            threadLocal.set(li);
        }
        System.out.println("[Thread-" + Thread.currentThread().getName() + "], list=" + threadLocal.get());
    }

    public static void main(String[] args) throws Exception {
        Thread ta = new Thread(new ThreadLocalWithSharedInstance(), "a");
        Thread tb = new Thread(new ThreadLocalWithSharedInstance(), "b");
        Thread tc = new Thread(new ThreadLocalWithSharedInstance(), "c");
        ta.start(); ta.join();
        tb.start(); tb.join();
        tc.start(); tc.join();
    }
}

以上程序最終的輸出如下:

[Thread-a], list=[a_2, a_7, a_4, a_5, a_7]
[Thread-b], list=[a_2, a_7, a_4, a_5, a_7, b_3, b_3, b_4, b_7, b_7]
[Thread-c], list=[a_2, a_7, a_4, a_5, a_7, b_3, b_3, b_4, b_7, b_7, c_8, c_3, c_4, c_7, c_5]

可以看到雖然使用了 ThreadLocal 修飾,但是 list 還是以共享的方式在多個線程之間被訪問,如果不加控制則會存在線程安全問題。

再來看 第 2 個問題,相對問題 1 來說引入線程池就更加可怕,因為大部分時候我們都不會意識到問題的存在,直到代碼暴露出奇怪的現象。這一場景并沒有違背線程私有的本質,只是一個線程被復用來處理多個業務,而這個被線程私有的對象也會在多個業務之間被共享。例如:

public class ThreadLocalWithThreadPool implements Callable<Boolean> {

    private static final int NCPU = Runtime.getRuntime().availableProcessors();

    private ThreadLocal<List<String>> threadLocal = ThreadLocal.withInitial(() -> {
        System.out.println("thread-" + Thread.currentThread().getId() + " init thread local");
        return new ArrayList<>();
    });

    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 5; i++) {
            List<String> li = threadLocal.get();
            li.add(Thread.currentThread().getId() + "_" + RandomUtils.nextInt(0, 10));
            threadLocal.set(li);
        }
        System.out.println("[Thread-" + Thread.currentThread().getId() + "], list=" + threadLocal.get());
        return true;
    }

    public static void main(String[] args) throws Exception {
        System.out.println("cpu core size : " + NCPU);
        List<Callable<Boolean>> tasks = new ArrayList<>(NCPU * 2);
        ThreadLocalWithThreadPool tl = new ThreadLocalWithThreadPool();
        for (int i = 0; i < NCPU * 2; i++) {
            tasks.add(tl);
        }
        ExecutorService es = Executors.newFixedThreadPool(2);
        List<Future<Boolean>> futures = es.invokeAll(tasks);
        for (final Future<Boolean> future : futures) {
            future.get();
        }
        es.shutdown();
    }
}

以上程序的最終輸出如下:

cpu core size : 8
thread-12 init thread local
thread-11 init thread local
[Thread-12], list=[12_8, 12_8, 12_4, 12_0, 12_1]
[Thread-11], list=[11_3, 11_3, 11_4, 11_8, 11_4]
[Thread-12], list=[12_8, 12_8, 12_4, 12_0, 12_1, 12_6, 12_7, 12_8, 12_8, 12_8]
[Thread-11], list=[11_3, 11_3, 11_4, 11_8, 11_4, 11_0, 11_2, 11_1, 11_7, 11_9]
[Thread-12], list=[12_8, 12_8, 12_4, 12_0, 12_1, 12_6, 12_7, 12_8, 12_8, 12_8, 12_8, 12_2, 12_8, 12_0, 12_6]
[Thread-11], list=[11_3, 11_3, 11_4, 11_8, 11_4, 11_0, 11_2, 11_1, 11_7, 11_9, 11_0, 11_6, 11_1, 11_2, 11_9]
[Thread-12], list=[12_8, 12_8, 12_4, 12_0, 12_1, 12_6, 12_7, 12_8, 12_8, 12_8, 12_8, 12_2, 12_8, 12_0, 12_6, 12_6, 12_3, 12_3, 12_1, 12_1]
[Thread-11], list=[11_3, 11_3, 11_4, 11_8, 11_4, 11_0, 11_2, 11_1, 11_7, 11_9, 11_0, 11_6, 11_1, 11_2, 11_9, 11_7, 11_5, 11_0, 11_6, 11_9]
[Thread-12], list=[12_8, 12_8, 12_4, 12_0, 12_1, 12_6, 12_7, 12_8, 12_8, 12_8, 12_8, 12_2, 12_8, 12_0, 12_6, 12_6, 12_3, 12_3, 12_1, 12_1, 12_0, 12_0, 12_1, 12_9, 12_5]
[Thread-11], list=[11_3, 11_3, 11_4, 11_8, 11_4, 11_0, 11_2, 11_1, 11_7, 11_9, 11_0, 11_6, 11_1, 11_2, 11_9, 11_7, 11_5, 11_0, 11_6, 11_9, 11_2, 11_7, 11_0, 11_8, 11_0]
[Thread-12], list=[12_8, 12_8, 12_4, 12_0, 12_1, 12_6, 12_7, 12_8, 12_8, 12_8, 12_8, 12_2, 12_8, 12_0, 12_6, 12_6, 12_3, 12_3, 12_1, 12_1, 12_0, 12_0, 12_1, 12_9, 12_5, 12_3, 12_6, 12_6, 12_0, 12_9]
[Thread-11], list=[11_3, 11_3, 11_4, 11_8, 11_4, 11_0, 11_2, 11_1, 11_7, 11_9, 11_0, 11_6, 11_1, 11_2, 11_9, 11_7, 11_5, 11_0, 11_6, 11_9, 11_2, 11_7, 11_0, 11_8, 11_0, 11_0, 11_9, 11_2, 11_7, 11_2]
[Thread-12], list=[12_8, 12_8, 12_4, 12_0, 12_1, 12_6, 12_7, 12_8, 12_8, 12_8, 12_8, 12_2, 12_8, 12_0, 12_6, 12_6, 12_3, 12_3, 12_1, 12_1, 12_0, 12_0, 12_1, 12_9, 12_5, 12_3, 12_6, 12_6, 12_0, 12_9, 12_5, 12_7, 12_7, 12_9, 12_7]
[Thread-11], list=[11_3, 11_3, 11_4, 11_8, 11_4, 11_0, 11_2, 11_1, 11_7, 11_9, 11_0, 11_6, 11_1, 11_2, 11_9, 11_7, 11_5, 11_0, 11_6, 11_9, 11_2, 11_7, 11_0, 11_8, 11_0, 11_0, 11_9, 11_2, 11_7, 11_2, 11_4, 11_9, 11_7, 11_5, 11_5]
[Thread-12], list=[12_8, 12_8, 12_4, 12_0, 12_1, 12_6, 12_7, 12_8, 12_8, 12_8, 12_8, 12_2, 12_8, 12_0, 12_6, 12_6, 12_3, 12_3, 12_1, 12_1, 12_0, 12_0, 12_1, 12_9, 12_5, 12_3, 12_6, 12_6, 12_0, 12_9, 12_5, 12_7, 12_7, 12_9, 12_7, 12_6, 12_1, 12_7, 12_8, 12_7]
[Thread-11], list=[11_3, 11_3, 11_4, 11_8, 11_4, 11_0, 11_2, 11_1, 11_7, 11_9, 11_0, 11_6, 11_1, 11_2, 11_9, 11_7, 11_5, 11_0, 11_6, 11_9, 11_2, 11_7, 11_0, 11_8, 11_0, 11_0, 11_9, 11_2, 11_7, 11_2, 11_4, 11_9, 11_7, 11_5, 11_5, 11_8, 11_5, 11_0, 11_2, 11_2]

示例中,我用一個大小為 2 的線程池進行了模擬,可以看到初始化方法被調用了兩次,所有線程的操作都是復用這兩個線程。

回憶一下前文所說的,ThreadLocal 的本質就是為每個線程維護一個線程私有的內存數據庫來記錄線程私有的對象,但是在線程池情況下線程是會被復用的,也就是說線程私有的內存數據庫也會被復用,如果在一個線程被使用完準備回放到線程池中之前,我們沒有對記錄在數據庫中的數據執行清理,那么這部分數據就會被下一個復用該線程的業務看到,從而間接的共享了該部分數據。

最后我們再來看一下 第 3 個問題,我們嘗試將 ThreadLocal 對象用 static 關鍵字進行修飾:

public class ThreadLocalWithStaticEmbellish implements Runnable {

    private static final int NCPU = Runtime.getRuntime().availableProcessors();

    private static ThreadLocal<List<String>> threadLocal = ThreadLocal.withInitial(() -> {
        System.out.println("thread-" + Thread.currentThread().getName() + " init thread local");
        return new ArrayList<>();
    });

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            List<String> li = threadLocal.get();
            li.add(Thread.currentThread().getId() + "_" + RandomUtils.nextInt(0, 10));
            threadLocal.set(li);
        }
        System.out.println("[Thread-" + Thread.currentThread().getName() + "], list=" + threadLocal.get());
    }

    public static void main(String[] args) throws Exception {
        ThreadLocalWithStaticEmbellish tl = new ThreadLocalWithStaticEmbellish();
        for (int i = 0; i < NCPU + 1; i++) {
            Thread thread = new Thread(tl, String.valueOf((char) (i + 97)));
            thread.start(); thread.join();
        }
    }
}

以上程序的最終輸出如下:

thread-a init thread local
[Thread-a], list=[11_4, 11_4, 11_4, 11_8, 11_0]
thread-b init thread local
[Thread-b], list=[12_0, 12_9, 12_0, 12_3, 12_3]
thread-c init thread local
[Thread-c], list=[13_6, 13_7, 13_5, 13_2, 13_0]
thread-d init thread local
[Thread-d], list=[14_1, 14_5, 14_5, 14_9, 14_2]
thread-e init thread local
[Thread-e], list=[15_4, 15_2, 15_6, 15_0, 15_8]
thread-f init thread local
[Thread-f], list=[16_7, 16_3, 16_8, 16_0, 16_0]
thread-g init thread local
[Thread-g], list=[17_6, 17_3, 17_8, 17_7, 17_1]
thread-h init thread local
[Thread-h], list=[18_0, 18_4, 18_5, 18_9, 18_3]
thread-i init thread local
[Thread-i], list=[19_7, 19_3, 19_7, 19_2, 19_0]

由程序運行結果可以看到 static 修飾并沒有引出什么問題,實際上這也是很容易理解的,ThreadLocal 采用 static 修飾僅僅是讓數據庫中記錄的 key 是一樣的,但是每個線程的內存數據庫還是私有的,并沒有被共享,就像不同的公司都有自己的用戶信息表,即使一些公司之間的用戶 ID 是一樣的,但是對應的用戶數據卻是完全隔離的。

以上例子演示了一開始拋出的 3 個問題,其中問題 1 和問題 2 都是 ThreadLocal 使用過程中的小地雷。例子舉的不一定恰當,實際中可能也不一定會如示例中這樣去使用 ThreadLocal,主要還是為了傳達一些意識。如果明白了 ThreadLocal 的內部實現細節,就能夠很自然的繞過這些小地雷。

真的會內存泄露嗎

關于 ThreadLocal 導致內存泄露的問題,曾經有一段時間在網上爭得沸沸揚揚,那么到底會不會導致內存泄露呢?這里先給出答案:

如果使用不恰當,存在內存泄露的可能性

我們來分析一下內存泄露的條件和原因,在最開始看 ThreadLocal 源碼的時候,我就有一個疑問,ThreadLocal 為什么要專門實現 ThreadLocalMap,而不是采用已有的 HashMap 代替

后來分析具體實現時看到執行存儲時的 key 為當前 ThreadLocal 對象,不需要專門指定 key 能夠在一定程度上簡化使用,但這并不足以為此專門去實現 ThreadLocalMap。繼續閱讀我發現 ThreadLocalMap 在實現 Entry 的時候有些奇怪,居然繼承了 WeakReference:

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

從而讓 key 成為一個弱引用,我們知道弱引用對象擁有非常短暫的生命周期,在垃圾收集器線程掃描其所管轄的內存區域過程中,一旦發現了弱引用對象,不管當前內存空間是否足夠都會回收它的內存。也就是說這樣的設計會很容易導致 ThreadLocal 對象被回收,線程所執行任務的時間長度是不固定的,這樣的設計能夠方便垃圾收集器回收線程私有的變量。

由此可以看出作者這樣設計的目的是為了防止內存泄露,那怎么就變成了被很多文章所分析的是內存泄漏的導火索呢?這些文章的共同觀點就是 key 被回收了,但是 value 是一個強引用沒有被回收,這些 value 就變成了一個個的僵尸。這樣的分析沒有錯,value 確實存在,且和線程是同生命周期的,但是如下策略可以保證盡量避免內存泄露:

  1. ThreadLocal 在每次執行 get 和 set 操作的時候都會去清理 key 為 null 的 value 值。

  2. value 與線程同生命周期,線程死亡之時,也是 value 被 GC 之日。

策略 1 沒啥好說的,看看源碼就知道,我們來舉例驗證一下策略 2:

public class ThreadLocalWithMemoryLeak implements Callable<Boolean> {

    private class My50MB {

        private byte[] buffer = new byte[50 * 1024 * 1024];

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("gc my 50 mb");
        }
    }

    private class MyThreadLocal<T> extends ThreadLocal<T> {

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("gc my thread local");
        }
    }

    private MyThreadLocal<My50MB> threadLocal = new MyThreadLocal<>();

    @Override
    public Boolean call() throws Exception {
        System.out.println("Thread-" + Thread.currentThread().getId() + " is running");
        threadLocal.set(new My50MB());
        threadLocal = null;
        return true;
    }

    public static void main(String[] args) throws Exception {
        ExecutorService es = Executors.newCachedThreadPool();
        Future<Boolean> future = es.submit(new ThreadLocalWithMemoryLeak());
        future.get();

        // gc my thread local
        System.out.println("do gc");
        System.gc();
        TimeUnit.SECONDS.sleep(1);

        // sleep 60s
        System.out.println("sleep 60s");
        TimeUnit.SECONDS.sleep(60);

        // gc my 50 mb
        System.out.println("do gc");
        System.gc();

        es.shutdown();
    }

}

以上程序的最終輸出如下:

Thread-11 is running
do gc
gc my thread local
sleep 60s
do gc
gc my 50 mb

可以看到 value 最終還是被 GC 了,雖然第 1 次 GC 的時候沒有被回收,這也驗證 value 和線程是同生命周期的,之所以示例中等待 60 秒是因為 Executors#newCachedThreadPool 中的線程默認生命周期是 60 秒,如果生命周期內該線程沒有被再次復用則會死亡,我們這里就是要等待線程死亡,一但線程死亡,value 也就被 GC 了。

所以 出現內存泄露的前提必須是持有 value 的線程一直存活,這在使用線程池時是很正常的,在這種情況下 value 一直不會被 GC,因為線程對象與 value 之間維護的是強引用。此外就是 后續線程執行的業務一直沒有調用 ThreadLocal 的 get 或 set 方法,導致不會主動去刪除 key 為 null 的 value 對象,在滿足這兩個條件下 value 對象一直常駐內存,所以存在內存泄露的可能性。

那么我們應該怎么避免呢?前面我們分析過線程池情況下使用 ThreadLocal 存在小地雷,這里的內存泄露一般也都是發生在線程池的情況下,所以在使用 ThreadLocal 時,對于不再有效的 value 主動調用一下 remove 方法來進行清除,從而消除隱患,這也算是最佳實踐吧。

InheritableThreadLocal 又是什么鬼

InheritableThreadLocal 繼承自 ThreadLocal,實現上也比較簡單(如下),那么 InheritableThreadLocal 與 ThreadLocal 到底有什么區別呢?

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    @Override
    protected T childValue(T parentValue) {
        return parentValue;
    }

    @Override
    ThreadLocalMap getMap(Thread t) {
        return t.inheritableThreadLocals;
    }

    @Override
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

在開始分析之前,我們先演示一個 ThreadLocal 的案例,如下:

private static ThreadLocal<String> tl = new ThreadLocal<>();

public static void main(String[] args) {
    tl.set("zhenchao");
    System.out.println("Main thread: " + tl.get());
    Thread thread = new Thread(() -> System.out.println("Sub thread: " + tl.get()));
    thread.start();
}

運行上述示例,輸出如下:

Main thread: zhenchao
Sub thread: null

可以看出,子線程拿不到主線程設置的 ThreadLocal 變量,當然這也是可以理解的,畢竟主線程和子線程之間仍然是兩個線程,但是在一些場景下我們希望對于主線程和子線程這種關系而言,ThreadLocal 變量能夠被繼承。這個時候就可以使用 InheritableThreadLocal 來實現,對于上述示例而言,只需要將 ThreadLocal 改為 InheritableThreadLocal 即可,具體實現比較簡單,讀者可以自己嘗試一下。

下面我們來分析一下 InheritableThreadLocal 是如何做到讓 ThreadLocal 變量在主線程和子線程之間進行繼承的。由 InheritableThreadLocal 的實現來看,InheritableThreadLocal 使用了 inheritableThreadLocals 變量替換了 ThreadLocal 的 threadLocals 變量,而這兩個變量都是 ThreadLocalMap 類型。子線程在初始化時會判斷父線程的 inheritableThreadLocals 是否為 null,如果不為 null,則使用父類的 inheritableThreadLocals 變量初始化自己的 inheritableThreadLocals,實現如下(位于 Thread#init 方法中):

// 如果父線程的 inheritableThreadLocals 變量不為空,則復制給子線程
if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
    this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}

ThreadLocal#createInheritedMap 的實現如下:

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                // 調用 InheritableThreadLocal 的 childValue 方法
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null) {
                    h = nextIndex(h, len);
                }
                table[h] = c;
                size++;
            }
        }
    }
}

方法 InheritableThreadLocal#childValue 的實現只是簡單返回了父線程中的值,所以上述過程本質上就是一個拷貝父線程中 ThreadLocal 變量值的過程。

到此,相信大家對“怎么理解ThreadLocal的實現機制”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

南乐县| 千阳县| 金华市| 乡城县| 武穴市| 乌拉特前旗| 寿阳县| 涪陵区| 大名县| 团风县| 林芝县| 灌南县| 长宁县| 黄陵县| 阿拉善右旗| 贵定县| 大足县| 岫岩| 呼和浩特市| 富阳市| 淮滨县| 垫江县| 凤翔县| 博湖县| 华蓥市| 招远市| 桐庐县| 奎屯市| 洛隆县| 潮安县| 冀州市| 纳雍县| 枞阳县| 山西省| 壤塘县| 忻州市| 德钦县| 滦平县| 田林县| 嘉黎县| 迭部县|