天天看點

【Java并發程式設計】面試常考的ThreadLocal,超詳細源碼學習

目錄

  • ThreadLocal是啥?用來幹啥?
  • ThreadLocal的簡單使用
  • ThreadLocal的實作思路?
  • ThreadLocal常見方法源碼分析
    • ThreadLocal.set(T value)
    • ThreadLocal.get()
    • ThreadLocal.remove()
  • ThreadLocalMap源碼分析
    • ThreadLocalMap結構分析
    • ThreadLocalMap的Hash算法
    • ThreadLocalMap.set()
    • ThreadLocalMap.resize()擴容
    • ThreadLocalMap.get()
    • ThreadLocalMap.remove()
    • ThreadLocalMap.replaceStaleEntry()
    • ThreadLocalMap過期key清理流程
      • 探測式清理expungeStaleEntry
      • 啟發式清理cleanSomeSlots
  • ThreadLocal的記憶體洩漏問題
    • 強引用與弱引用的差別
    • ThreadLocal記憶體洩漏如何造成?
    • 如何解決這個問題呢?
    • 那為什麼要設計使用弱引用呢?強引用不香麼?
  • ThreadLocal可以解決哪些問題?
    • 解決并發問題
    • 解決資料存儲問題
  • InheritableThreadLocal是啥?
  • 參考
本文基于JDK1.8

ThreadLocal是啥?用來幹啥?

public class Thread implements Runnable {
	//線程内部局部變量
    ThreadLocal.ThreadLocalMap threadLocals = null;
	//子線程繼承父線程的變量
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
           

Java官方對ThreadLocal類的定義如下:

  • ThreadLocal類用于提供線程内部的局部變量。
  • 這種變量在多線程環境下通路(通過

    get

    set

    方法通路)時能保證各個線程的變量相對獨立于其他線程内的變量。
  • ThreadLocal

    執行個體通常來說都是

    private static

    類型的,用于關聯線程和線程上下文。

ThreadLocal的作用:

ThreadLocal

的作用是提供線程内的局部變量,不同的線程之間不會互相幹擾,這種變量線上程的生命周期内起作用,減少同一個線程内多個函數或元件之間一些公共變量的傳遞的複雜度。

ThreadLocal的簡單使用

/**
 * 官方demo,測試ThreadLocal的用法
 * 
 * @author Summerday
 */
public class ThreadLocalTest {

    public static void main(String[] args) {
        // main Thread
        incrementSameThreadId();
        new Thread(ThreadLocalTest::incrementSameThreadId).start();
        new Thread(ThreadLocalTest::incrementSameThreadId).start();
    }

    private static void incrementSameThreadId() {
        try {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread() + "_" + i + ",threadId:" + ThreadLocalId.get());
            }
        } finally {
            // 使用後請清除
            ThreadLocalId.remove();
        }
    }

}

class ThreadLocalId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return nextId.getAndIncrement();
        }
    };

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }

    // remove currentid
    public static void remove() {
        threadId.remove();
    }
}
           
//輸出
Thread[main,5,main]_0,threadId:0
Thread[main,5,main]_1,threadId:0
Thread[main,5,main]_2,threadId:0
Thread[Thread-0,5,main]_0,threadId:1
Thread[Thread-0,5,main]_1,threadId:1
Thread[Thread-0,5,main]_2,threadId:1
Thread[Thread-1,5,main]_0,threadId:2
Thread[Thread-1,5,main]_1,threadId:2
Thread[Thread-1,5,main]_2,threadId:2
           

ThreadLocal的實作思路?

【Java并發程式設計】面試常考的ThreadLocal,超詳細源碼學習
  • Thread類中有一個類型為

    ThreadLocal.ThreadLocalMap

    的執行個體變量threadLocals,意味着每個線程都有一個自己的ThreadLocalMap。
  • 可以簡單地将key視作ThreadLocal,value為代碼中放入的值(實際上key并不是ThreadLocal本身,而是它的一個弱引用)。
  • 每個線程在往某個ThreadLocal裡塞值的時候,都會往自己的ThreadLocalMap裡存,讀也是以某個ThreadLocal作為引用,在自己的map裡找對應的key,進而實作了線程隔離。

ThreadLocal常見方法源碼分析

ThreadLocal.set(T value)

public void set(T value) {
        //擷取到目前的線程對象
        Thread t = Thread.currentThread();
        //進而擷取此線程對象中維護的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        //如果ThreadLocalMap存在,則以目前的ThreadLocal為key,value作為值設定entry
        if (map != null)
            map.set(this, value);
        else
            //調用createMap進行ThreadLocalMap對象的初始化,并将此實體作為第一個值
            createMap(t, value);
    }
	//建立一個與線程t關聯的ThreadLocalMap
	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
           

ThreadLocal.get()

public T get() {
        //擷取到目前的線程對象
        Thread t = Thread.currentThread();
        //進而擷取此線程對象中維護的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        //如果此map存在
        if (map != null) {
            //以目前的ThreadLocal 為 key,調用getEntry擷取對應的存儲實體e
            ThreadLocalMap.Entry e = map.getEntry(this);
            //找到對應的存儲實體 e
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果map不存在,調用setInitialValue進行初始化
        return setInitialValue();
    }

    private T setInitialValue() {
        //調用initialValue擷取初始化的值
        T value = initialValue();
        //擷取目前線程對象
        Thread t = Thread.currentThread();
        //擷取此線程對象中維護的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        //如果此map存在
        if (map != null)
            //存在則調用map.set設定此實體entry
            map.set(this, value);
        else
            ////調用createMap進行ThreadLocalMap對象的初始化,并将此實體作為第一個值
            createMap(t, value);
        return value;
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
           

ThreadLocal.remove()

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             //以目前ThreadLocal為key删除對應的實體entry
             m.remove(this);
     }

           

可以發現,ThreadLocal的這些方法,都是通過目前線程找到對應的map,其實都是對其維護的ThreadLocalMap這個對象進行的操作,這些細節後面探讨。

ThreadLocalMap源碼分析

ThreadLocalMap結構分析

ThreadLocalMap的底層實作是一個定制的自定義HashMap:

  • Entry[] table

    :底層是一個Entry類型的數組,必要時需要進行擴容,數組的長度必須是2的n次幂,為了在Hash時效率更高:當n為2的n此幂時,

    hashCode % len

    hashCode & (len -1)

    效果相同,但位運算效率更高。
  • int threshold

    :下次擴容時的門檻值,

    threshold = len * 2 / 3

    。當

    size >= threshold

    時,周遊

    table

    并删除

    key

    null

    的元素,如果删除後

    size >= threshold * 3 / 4

    時,需要對

    table

    進行擴容。
  • Entry:是哈希表存儲的核心元素,Entry繼承了弱引用。
    • ThreadLocal<?> k:目前存儲的ThreadLocal執行個體。
    • object value:目前ThreadLocal對應存儲的值。

Entry繼承了弱引用,這樣會導緻

Entry[]table

中的每個資料可能會有三種狀态:

  • entry不為null,且key不為null,正常資料。
  • entry不為null,但key為null,表示過期資料。
  • entry == null。
static class ThreadLocalMap {

        /**
         * 實體Entry在此hash map中是繼承弱引用 WeakReference, 
         * 使用ThreadLocal 作為 key 鍵.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** 目前 ThreadLocal 對應儲存的值value. */
            Object value;

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

        /**
         * 初始容量大小 16 -- 必須是2的n次方.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * 底層哈希表 table, 必要時需要進行擴容.
         * 底層哈希表 table.length 長度必須是2的n次方.
         */
        private Entry[] table;

        /**
         * 實際存儲鍵值對元素個數 entries.
         */
        private int size = 0;

        /**
         * 下一次擴容時的門檻值
         */
        private int threshold; // 預設為 0

        /**
         * 設定觸發擴容時的門檻值 threshold
         * 門檻值 threshold = 底層哈希表table的長度 len * 2 / 3
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * 擷取該位置i對應的下一個位置index
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * 擷取該位置i對應的上一個位置index
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

    }
           

ThreadLocalMap的Hash算法

public class ThreadLocal<T> {

    private final int threadLocalHashCode = nextHashCode();

    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    //...省略get set remove等方法
    
    
    static class ThreadLocalMap {
        //2的幂    len % c = len & (c - 1)
        private static final int INITIAL_CAPACITY = 16;
        
        //ThreadLocalMaps是延遲加載的,在有entry實體存放時,才初始化建立一次。
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            // 雜湊演算法 , 利用數組容量2的幂次的特性,位運算快速得到位置
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
    }
}
           

ThreadLocalMap使用線性探測法來解決散列沖突,而Entry[]table其實在邏輯上也就是可以想象成一個環,這也可以看出nextIndex和prevIndex兩個方法,是擷取環形意義上的下一個位置。

每當建立一個ThreadLocal對象,nextHashCode就會增長

0x61c88647

0x61c88647

這個值非常特殊,被稱作斐波那契數,它能使hash分布更加均勻,線性探測時就能更快探測到下一個臨近可用的slot,進而保證效率。

ThreadLocalMap.set()

https://snailclimb.gitee.io/javaguide/#/docs/java/Multithread/ThreadLocal

private void set(ThreadLocal<?> key, Object value) {

    //set方法需要考慮到hash碰撞,相對get方法比較複雜一些
    Entry[] tab = table;
    int len = tab.length;
    //得到在table中的索引位置
    int i = key.threadLocalHashCode & (len-1);

    //循環周遊
    for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
        //擷取目前位置的ThreadLocal
        ThreadLocal<?> k = e.get();
        //如果key一緻,則直接賦予新值【替換操作】,并退出
        if (k == key) {
            e.value = value;
            return;
        }
        //目前位置的key為null【過期資料】,調用replaceStaleEntry方法,并退出
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //周遊過程中,遇到entry == null 的情況【沒有資料沖突】,直接使用這個桶
    tab[i] = new Entry(key, value);
    int sz = ++size;
    //調用cleanSomeSlots啟發式清理操作,清理散列數組中Entry的key過期資料
    //清理完成後,未清理到任何資料,且size超過了門檻值,進行rehash
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        //rehash不等于resize!
        rehash();
}

private void rehash() {
    // 進行一輪探測式清理【全量清理】
    expungeStaleEntries();

    // 清理完成後,如果滿足size >= threshold - threshold / 4,執行擴容邏輯
    // threshold預設是 len * 2/3    size >= len / 2 預設執行擴容
    if (size >= threshold - threshold / 4)
        resize();
}
           

ThreadLocalMap.resize()擴容

//将表的容量加倍
private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;
	//逐一周遊舊的哈希表的每一個entry,重新配置設定至新的哈希表中
    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null; // 清除無效entry的value值,幫助GC回收
            } else {
                 // 線性探測來存放Entry
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }
	//重新計算門檻值
    setThreshold(newLen);
    size = count;
    table = newTab;
}
           
  • 擴容後的tab大小為oldLen * 2。
  • 周遊老數組,重新計算hash位置,放到新的tab數組中。
  • 如果出現hash沖突,則往後找到最近的entry為null的槽位。
  • 周遊完成之後oldTab中所有的資料置如新的tab。
  • 重新計算tab下次擴容的門檻值。

ThreadLocalMap.get()

private Entry getEntry(ThreadLocal<?> key) {
    //通過key計算出散清單中的位置
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    //如果key一緻,命中直接傳回
    if (e != null && e.get() == key)
        return e;
    else
        //不一緻,調用方法繼續找
        return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        //遇到無效的slot,執行一次探測式的清理
        if (k == null)
            expungeStaleEntry(i);
        else
            //index後移
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
           

ThreadLocalMap.remove()

private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    // 計算對應threalocal的存儲位置
    int i = key.threadLocalHashCode & (len-1);
    // 循環周遊table對應該位置的實體,查找對應的threadLocal
    for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
        // 如果key threadLocal一緻,則證明找到對應的threadLocal
        if (e.get() == key) {
            // 執行清除操作
            e.clear();
            // 清除此位置的實體
            expungeStaleEntry(i);
            // 結束
            return;
        }
    }
}
           

ThreadLocalMap.replaceStaleEntry()

在set方法中,循環尋找桶時,找到過期的key,将會調用該方法,replaceStaleEntry方法提供了替換過期資料的功能:

// 在執行set操作時,擷取對應的key,并替換過期的entry
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;

    // 往前找到table中第一個過期的實體的下标
    // 清理整個table範圍,避免因為垃圾回收帶來的連續增長哈希的危險
    
    // 記錄slotToExpunge 開始探測式清理過期資料的開始下标
    int slotToExpunge = staleSlot;
    
    //向前周遊查找第一個過期的實體下标
    for (int i = prevIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = prevIndex(i, len))
        if (e.get() == null)
            slotToExpunge = i;

    // 向後周遊查找 key一緻的ThreadLocal或 key為null 的
    for (int i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();

        // 如果找到key,【将它跟新的過期資料交換】
        if (k == key) {
            
            e.value = value;

            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            // 兩個索引重合,表明在【整個掃描過程中】前+後掃描  的時候并沒有找到過期的key
            if (slotToExpunge == staleSlot)
                //修改開始探測式清理過期資料的下标為目前循環的index
                slotToExpunge = i;
            // 從slotToExpunge開始做一次啟發式清理
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }

        // 如果目前的slot已經無效,并且向前掃描過程中沒有無效slot,則更新slotToExpunge為目前位置
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }

    // 最後key仍然沒有找到,則将要設定的新實體放置在原過期的實體對應的位置上
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    // 在探測過程中如果發現任何無效slot,則做一次清理(探測式清理+啟發式清理)
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
           

ThreadLocalMap過期key清理流程

探測式清理expungeStaleEntry

expungeStaleEntry方法将會周遊散列數組,從開始位置向後探測清理過期資料,将過期資料的Entry設定為null,沿途中碰到未過期的資料則将此資料rehash後重新在table中定位。

如果定位的位置已經有了資料,則會将未過期的資料放到最靠近此位置的Entry==null的桶中,使rehash後的Entry資料距離正确的桶的位置更近一些。

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 因為entry對應的ThreadLocal已經被回收,value設為null,顯式斷開強引用
    tab[staleSlot].value = null;
    // 顯式設定該entry為null,以便垃圾回收
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    //向後周遊
    for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        //遇到k == null 的過期資料,清空該槽位
        if (k == null) { 
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            // key沒有過期,重新計算目前key的下标位置是不是目前槽位的下标位置
            int h = k.threadLocalHashCode & (len - 1);
            // 如果不是,說明産生了hash沖突
            if (h != i) {
                tab[i] = null;

                //以新計算出正确的槽位位置往後疊代,找到最近一個可以存放entry的位置.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    //經過疊代之後,有hash沖突的資料的entry位置會更加靠近正确的位置,查詢效率更高。
    return i;
}
           

啟發式清理cleanSomeSlots

在添加Entry或過期元素被清除時調用:

  • 如果沒有過期資料,隻要掃描logN次即可,這個算法的時間複雜度為O(logN)。
  • 如果有過期資料,需要将n置為table的長度len,做一次探測式清理,再從下一個空的slot開始繼續掃描。
// i對應的entry是非無效的
// n用于控制掃描次數
private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        //從i的下一個開始,因為i不是無效的
        i = nextIndex(i, len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            //擴大掃描因子
            n = len;
            removed = true;
            //清理一個連續段
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}
           

ThreadLocal的記憶體洩漏問題

我們需要明确ThreadLocalMap的結構,ThreadLocalMap中的存儲實體Entry使用ThreadLocal作為key,但這個Entry繼承弱引用WeakReference。

強引用與弱引用的差別

弱引用與強引用有啥差別?在這邊複習一下:引用類型有四種,強引用屬于第一檔,而弱引用屬于第三檔。

  • 強引用:将一個對象指派給一個引用變量,這個引用變量就是一個強引用,當一個對象被強引用對象引用時,處于可達狀态,就不會被垃圾回收機制回收。
  • 弱引用:通過WeakReference實作,弱引用的生命周期很短,隻要垃圾回收機制運作,不管JVM的記憶體空間是否足夠,總會回收該對象占用的記憶體。

舉個例子:

A a = new A(); 
a = null;
           

由于a = null,一段時間之後,Java垃圾回收機制就會将a對應的記憶體空間回收。

B b = new B(a);
a = null;
           

當a被設定為null之後,GC卻并不會回收a的記憶體空間,原因在于:盡管a已經為null,但對象B仍然持有對a的強引用,是以這時a這塊記憶體就出現了記憶體洩漏,因為無法回收,也無法使用。

解決的辦法有兩種:

  • 強行讓b為null,這樣對于對象a就再沒有強引用指向它。
  • 讓a成為弱引用類型:

    WeakReference w = new WeakReference(a);

    ,對于弱引用,GC是可以回收a原先配置設定的記憶體空間的。

ThreadLocal記憶體洩漏如何造成?

ThreadLocalMap

使用

ThreadLocal

的弱引用作為

key

,如果一個

ThreadLocal

沒有外部強引用來引用它,那麼系統 GC 的時候,這個

ThreadLocal

勢必會被回收,這樣一來,

ThreadLocalMap

中就會出現

key

null

Entry

,就沒有辦法通路這些

key

null

Entry

value

如果目前線程再遲遲不結束的話,這些

key

null

Entry

value

就會一直存在一條強引用鍊:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永遠無法回收,造成記憶體洩漏。

ThreadLocalMap的設計中已經考慮到這種情況,也再get,set,remove等方法上做了預防:在調用之後都清除線程ThreadLocalMap所有key為null的value。

使用ThreadLocal發生記憶體洩漏的前提條件:

  1. ThreadLocal引用被設定為null,且後面沒有set,get,remove等操作。
  2. 線程一緻運作,不停止。【線程池】
  3. 觸發了垃圾回收。【MinorGC或FullGC】

如何解決這個問題呢?

  • ThreadLocal申明為

    private static final

    :private final盡量不讓他人修改變更引用,static類屬性,隻有在程式結束的時候才會被回收。
  • 每次使用完

    ThreadLocal

    ,都調用它的

    remove()

    方法,清除資料。

那為什麼要設計使用弱引用呢?強引用不香麼?

這個問題可以從如果使用強引用會引發什麼問題讨論:

如果使用強引用,也就是普通的key-value形式定義存儲結構,實質上就是将節點的生命周期與線程強行綁定,隻要線程沒有銷毀,節點在GC分析中一直處于可達狀态,無法被回收。

使用弱引用的好處在于,如果某個ThreadLocal已經沒有強引用可達,它就會被垃圾回收,ThreadLocalMap中對應的Entry也會失效,為ThreadLocalMap本身的垃圾清理提供了便利。

ThreadLocal可以解決哪些問題?

解決并發問題

使用

ThreadLocal

代替

synchronized

來保證線程安全。同步機制采用了“以時間換空間”的方式,而

ThreadLoca

l采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊通路,而後者為每一個線程都提供了一份變量,是以可以同時通路而互不影響。

Java7 的SimpleDateFormat不是線程安全的,可以通過ThreadLocal解決
public class DateUtil {
    private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static String formatDate(Date date) {
        return format1.get().format(date);
    }
}
           

解決資料存儲問題

ThreadLocal

為變量在每個線程中都建立了一個副本,是以每個線程可以通路自己内部的副本變量,不同線程之間不會互相幹擾。如一個

Parameter

對象的資料需要在多個子產品中使用,如果采用參數傳遞的方式,顯然會增加子產品之間的耦合性。此時我們可以使用

ThreadLocal

解決。

以下例子用于session的存儲
private static final ThreadLocal threadSession = new ThreadLocal();

    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }
           

InheritableThreadLocal是啥?

InheritableThreadLocal主要用于子線程建立時,需要自動繼承父線程的ThreadLocal變量,友善必要資訊的進一步傳遞。

實作原理是子線程是通過在父線程中通過調用

new Thread()

方法來建立子線程,

Thread#init

方法在

Thread

的構造方法中被調用。在

init

方法中拷貝父線程資料到子線程中

線程初始化代碼:

/**
     * 初始化一個線程.
     * 此函數有兩處調用,
     * 1、上面的 init(),不傳AccessControlContext,inheritThreadLocals=true
     * 2、傳遞AccessControlContext,inheritThreadLocals=false
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        //......(其他代碼)

        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

        //......(其他代碼)
    }

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

    /**
    * 建構一個包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
    * 該函數隻被 createInheritedMap() 調用.
    */
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        // ThreadLocalMap 使用 Entry[] table 存儲ThreadLocal
        table = new Entry[len];

        // 逐一複制 parentMap 的記錄
        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++;
                }
            }
        }
    }
           

參考

  • ThreadLocal源碼解讀
  • JAVA并發-自問自答學ThreadLocal
  • ThreadLocal萬字圖文全面解析!一篇文章徹底搞懂ThreadLocal
  • InheritableThreadLocal詳解
  • ThreadLocal原理分析與使用場景