ThreadLocal之深度解讀

微信公衆號:I am CR7
若有問題或建議,請在下方留言;
最近更新:2019-01-12php

前言

繼上一篇文章《Spring Cloud Netflix Zuul源碼分析之請求處理篇》中提到的RequestContext使用的兩大神器之一:ThreadLocal,本文特此進行深刻分析,爲你們掃清知識障礙。java

Hello World

在展開深刻分析以前,我們先來看一個官方示例:算法

出處來源於ThreadLocal類上的註釋,其中main方法是筆者加上的。數組

 1import java.util.concurrent.atomic.AtomicInteger;
2
3public class ThreadId {
4    // Atomic integer containing the next thread ID to be assigned
5    private static final AtomicInteger nextId = new AtomicInteger(0);
6
7    // Thread local variable containing each thread's ID
8    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
9        @Override
10        protected Integer initialValue() {
11            return nextId.getAndIncrement();
12        }
13    };
14
15    // Returns the current thread's unique ID, assigning it if necessary
16    public static int get() {
17        return threadId.get();
18    }
19
20    public static void main(String[] args) {
21        for (int i = 0; i < 5; i++) {
22            new Thread(new Runnable() {
23                @Override
24                public void run() {
25                    System.out.println("threadName=" + Thread.currentThread().getName() + ",threadId=" + ThreadId.get());
26                }
27            }).start();
28        }
29    }
30}
複製代碼

運行結果以下:bash

1threadName=Thread-0,threadId=0
2threadName=Thread-1,threadId=1
3threadName=Thread-2,threadId=2
4threadName=Thread-3,threadId=3
5threadName=Thread-4,threadId=4
複製代碼

我問:看完這個例子,您知道ThreadLocal是幹什麼的了嗎?
您答:不知道,沒感受,一個hello world的例子,徹底激發不了個人興趣。
您問:那個誰,你敢不敢舉一個生產級的、工做中真實能用的例子?
我答:得,您是"爺",您說啥我就作啥。還記得《Spring Cloud Netflix Zuul源碼分析之請求處理篇》中提到的RequestContext嗎?這就是一個生產級的運用啊。Zuul核心原理是什麼?就是將請求放入過濾器鏈中通過一個個過濾器的處理,過濾器之間沒有直接的調用關係,處理的結果都是存放在RequestContext裏傳遞的,而這個RequestContext就是一個ThreadLocal類型的對象啊!!!微信

 1public class RequestContext extends ConcurrentHashMap<StringObject{
2
3    protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
4        @Override
5        protected RequestContext initialValue() {
6            try {
7                return contextClass.newInstance();
8            } catch (Throwable e) {
9                throw new RuntimeException(e);
10            }
11        }
12    };
13
14    public static RequestContext getCurrentContext() {
15        if (testContext != nullreturn testContext;
16
17        RequestContext context = threadLocal.get();
18        return context;
19    }
20}
複製代碼

以Zuul中前置過濾器DebugFilter爲例:app

 1public class DebugFilter extends ZuulFilter {
2
3    @Override
4    public Object run() {
5        // 獲取ThreadLocal對象RequestContext
6        RequestContext ctx = RequestContext.getCurrentContext();
7        // 它是一個map,能夠放入數據,給後面的過濾器使用
8        ctx.setDebugRouting(true);
9        ctx.setDebugRequest(true);
10        return null;
11    }
12
13}
複製代碼

您問:那說了半天,它究竟是什麼,有什麼用,能不能給個概念?
我答:能!必須能!!!ide

What is this

它是啥?它是一個支持泛型的java類啊,拋開裏面的靜態內部類ThreadLocalMap不說,其實它沒幾行代碼,不信,您本身去看看。它用來幹啥?類上註釋說的很明白:源碼分析

  • 它能讓線程擁有了本身內部獨享的變量
  • 每個線程能夠經過get、set方法去進行操做
  • 能夠覆蓋initialValue方法指定線程獨享的值
  • 一般會用來修飾類裏private static final的屬性,爲線程設置一些狀態信息,例如user ID或者Transaction ID
  • 每個線程都有一個指向threadLocal實例的弱引用,只要線程一直存活或者該threadLocal實例能被訪問到,都不會被垃圾回收清理掉

愛提問的您,必定會有疑惑,demo裏只是調用了ThreadLocal.get()方法,它如何實現這偉大的一切呢?這就是筆者下面要講的內容,走着~~~post

我有個人map

話很少說,咱們來看get方法內部實現:

get()源碼
 1public T get() {
2    Thread t = Thread.currentThread();
3    ThreadLocalMap map = getMap(t);
4    if (map != null) {
5        ThreadLocalMap.Entry e = map.getEntry(this);
6        if (e != null) {
7            @SuppressWarnings("unchecked")
8            T result = (T)e.value;
9            return result;
10        }
11    }
12    return setInitialValue();
13}
複製代碼

邏輯很簡單:

  • 獲取當前線程內部的ThreadLocalMap
  • map存在則獲取當前ThreadLocal對應的value值
  • map不存在或者找不到value值,則調用setInitialValue,進行初始化
setInitialValue()源碼
 1private T setInitialValue({
2    T value = initialValue();
3    Thread t = Thread.currentThread();
4    ThreadLocalMap map = getMap(t);
5    if (map != null)
6        map.set(thisvalue);
7    else
8        createMap(t, value);
9    return value;
10}
複製代碼

邏輯也很簡單:

  • 調用initialValue方法,獲取初始化值【調用者經過覆蓋該方法,設置本身的初始化值】
  • 獲取當前線程內部的ThreadLocalMap
  • map存在則把當前ThreadLocal和value添加到map中
  • map不存在則建立一個ThreadLocalMap,保存到當前線程內部
時序圖

爲了便於理解,筆者特意畫了一個時序圖,請看:

get方法時序圖
get方法時序圖
小結

至此,您能回答ThreadLocal的實現原理了嗎?沒錯,map,一個叫作ThreadLocalMap的map,這是關鍵。每個線程都有一個私有變量,是ThreadLocalMap類型。當爲線程添加ThreadLocal對象時,就是保存到這個map中,因此線程與線程間不會互相干擾。總結起來,一句話:我有個人young,哦,不對,是我有個人map。弄清楚了這些,是否是使用的時候就自信了不少。可是,這是否是就意味着能夠大膽的去使用了呢?其實,不盡然,有一個「大坑」在等着你。

神奇的remove

那個「大坑」指的就是由於ThreadLocal使用不當,會引起內存泄露的問題。筆者給出兩段示例代碼,來講明這個問題。

代碼出處來源於Stack Overflow:stackoverflow.com/questions/1…

示例一:
 1public class MemoryLeak {
2
3    public static void main(String[] args{
4        new Thread(new Runnable() {
5            @Override
6            public void run(
{
7                for (int i = 0; i < 1000; i++) {
8                    TestClass t = new TestClass(i);
9                    t.printId();
10                    t = null;
11                }
12            }
13        }).start();
14    }
15
16    static class TestClass{
17        private int id;
18        private int[] arr;
19        private ThreadLocal<TestClass> threadLocal;
20        TestClass(int id){
21            this.id = id;
22            arr = new int[1000000];
23            threadLocal = new ThreadLocal<>();
24            threadLocal.set(this);
25        }
26
27        public void printId(){
28            System.out.println(threadLocal.get().id);
29        }
30    }
31}
複製代碼

運行結果:

 10
21
32
43
5...省略...
6440
7441
8442
9443
10444
11Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
12    at com.gentlemanqc.MemoryLeak$TestClass.<init>(MemoryLeak.java:33)
13    at com.gentlemanqc.MemoryLeak$1.run(MemoryLeak.java:16)
14    at java.lang.Thread.run(Thread.java:745)
複製代碼

對上述代碼稍做修改,請看:

 1public class MemoryLeak {
2
3    public static void main(String[] args{
4        new Thread(new Runnable() {
5            @Override
6            public void run(
{
7                for (int i = 0; i < 1000; i++) {
8                    TestClass t = new TestClass(i);
9                    t.printId();
10                    t.threadLocal.remove();
11                }
12            }
13        }).start();
14    }
15
16    static class TestClass{
17        private int id;
18        private int[] arr;
19        private ThreadLocal<TestClass> threadLocal;
20        TestClass(int id){
21            this.id = id;
22            arr = new int[1000000];
23            threadLocal = new ThreadLocal<>();
24            threadLocal.set(this);
25        }
26
27        public void printId(){
28            System.out.println(threadLocal.get().id);
29        }
30    }
31}
複製代碼

運行結果:

10
21
32
43
5...省略...
6996
7997
8998
9999
複製代碼

一個內存泄漏,一個正常完成,對比代碼只有一處不一樣:t = null改成了t.threadLocal.remove(); 哇,神奇的remove!!!筆者先留個懸念,暫且不去分析緣由。咱們先來看看上述示例中涉及到的兩個方法:set()和remove()。

set(T value)源碼
1public void set(value{
2    Thread t = Thread.currentThread();
3    ThreadLocalMap map = getMap(t);
4    if (map != null)
5        map.set(thisvalue);
6    else
7        createMap(t, value);
8}
複製代碼

邏輯很簡單:

  • 獲取當前線程內部的ThreadLocalMap
  • map存在則把當前ThreadLocal和value添加到map中
  • map不存在則建立一個ThreadLocalMap,保存到當前線程內部
remove源碼
1public void remove({
2    ThreadLocalMap m = getMap(Thread.currentThread());
3    if (m != null)
4     m.remove(this);
5}
複製代碼

就一句話,獲取當前線程內部的ThreadLocalMap,存在則從map中刪除這個ThreadLocal對象。

小結

講到這裏,ThreadLocal最經常使用的四種方法都已經說完了,細心的您是否是已經發現,每個方法都離不開一個類,那就是ThreadLocalMap。因此,要更好的理解ThreadLocal,就有必要深刻的去學習這個map。

無處不在的ThreadLocalMap

仍是老規矩,先來看看類上的註釋,翻譯過來就是這麼幾點:

  • ThreadLocalMap是一個自定義的hash map,專門用來保存線程的thread local變量
  • 它的操做僅限於ThreadLocal類中,不對外暴露
  • 這個類被用在Thread類的私有變量threadLocals和inheritableThreadLocals上
  • 爲了可以保存大量且存活時間較長的threadLocal實例,hash table entries採用了WeakReferences做爲key的類型
  • 一旦hash table運行空間不足時,key爲null的entry就會被清理掉

咱們來看下類的聲明信息:

 1static class ThreadLocalMap {
2
3    // hash map中的entry繼承自弱引用WeakReference,指向threadLocal對象
4    // 對於key爲null的entry,說明再也不須要訪問,會從table表中清理掉
5    // 這種entry被成爲「stale entries」
6    static class Entry extends WeakReference<ThreadLocal<?>> {
7        /** The value associated with this ThreadLocal. */
8        Object value;
9
10        Entry(ThreadLocal<?> k, Object v) {
11            super(k);
12            value = v;
13        }
14    }
15
16    /**
17     * The initial capacity -- MUST be a power of two.
18     */

19    private static final int INITIAL_CAPACITY = 16;
20
21    /**
22     * The table, resized as necessary.
23     * table.length MUST always be a power of two.
24     */

25    private Entry[] table;
26
27    /**
28     * The number of entries in the table.
29     */

30    private int size = 0;
31
32    /**
33     * The next size value at which to resize.
34     */

35    private int threshold; // Default to 0
36
37    /**
38     * Set the resize threshold to maintain at worst a 2/3 load factor.
39     */

40    private void setThreshold(int len) {
41        threshold = len * 2 / 3;
42    }
43
44    /**
45     * Construct a new map initially containing (firstKey, firstValue).
46     * ThreadLocalMaps are constructed lazily, so we only create
47     * one when we have at least one entry to put in it.
48     */

49    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
50        table = new Entry[INITIAL_CAPACITY];
51        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
52        table[i] = new Entry(firstKey, firstValue);
53        size = 1;
54        setThreshold(INITIAL_CAPACITY);
55    }
56}
複製代碼

當建立一個ThreadLocalMap時,實際上內部是構建了一個Entry類型的數組,初始化大小爲16,閾值threshold爲數組長度的2/3,Entry類型爲WeakReference,有一個弱引用指向ThreadLocal對象。

爲何Entry採用WeakReference類型?

Java垃圾回收時,看一個對象需不須要回收,就是看這個對象是否可達。什麼是可達,就是能不能經過引用去訪問到這個對象。(固然,垃圾回收的策略遠比這個複雜,這裏爲了便於理解,簡單給你們說一下)。

jdk1.2之後,引用就被分爲四種類型:強引用、弱引用、軟引用和虛引用。強引用就是咱們經常使用的Object obj = new Object(),obj就是一個強引用,指向了對象內存空間。當內存空間不足時,Java垃圾回收程序發現對象有一個強引用,寧願拋出OutofMemory錯誤,也不會去回收一個強引用的內存空間。而弱引用,即WeakReference,意思就是當一個對象只有弱引用指向它時,垃圾回收器無論當前內存是否足夠,都會進行回收。反過來講,這個對象是否要被垃圾回收掉,取決因而否有強引用指向。ThreadLocalMap這麼作,是不想由於本身存儲了ThreadLocal對象,而影響到它的垃圾回收,而是把這個主動權徹底交給了調用方,一旦調用方不想使用,設置ThreadLocal對象爲null,內存就能夠被回收掉。

內存溢出問題解答

至此,該作的鋪墊都已經完成了,此時,咱們能夠來看看上面那個內存泄漏的例子。示例中執行一次for循環裏的代碼後,對應的內存狀態:

內存狀態
內存狀態
  • t爲建立TestClass對象返回的引用,臨時變量,在一次for循環後就執行出棧了
  • thread爲建立Thread對象返回的引用,run方法在執行過程當中,暫時不會執行出棧

調用t=null後,雖然沒法再經過t訪問內存地址MemoryLeak

 1不能識別此Latex公式:
2TestClass@538,可是當前線程依舊存活,能夠經過thread指向的內存地址,訪問到Thread對象,從而訪問到ThreadLocalMap對象,訪問到value指向的內存空間,訪問到arr指向的內存空間,從而致使Java垃圾回收並不會回收int[1000000]@541這一片空間。那麼隨着循環屢次以後,不被回收的堆空間愈來愈大,最後拋出java.lang.OutOfMemoryError: Java heap space。
3
4您問:那爲何調用t.threadLocal.remove()就能夠呢?
5
6我答:這就得看remove方法裏究竟作了什麼了,請看:
7
image

8是否是恍然大悟?來看下調用remove方法以後的內存狀態:
9
image

10由於remove方法將referent和value都被設置爲null,因此ThreadLocal@540和Memory
複製代碼
TestClass@538對應的內存地址都變成不可達,Java垃圾回收天然就會回收這片內存,從而不會出現內存泄漏的錯誤。

小結

呼應文章開頭提到的《Spring Cloud Netflix Zuul源碼分析之請求處理篇》,其中就有一個很是重要的類:ZuulServlet,它就是典型的ThreadLocal在實際場景中的運用案例。請看:

 1public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
2    try {
3        init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
4        RequestContext context = RequestContext.getCurrentContext();
5        context.setZuulEngineRan();
6
7        try {
8            preRoute();
9        } catch (ZuulException e) {
10            error(e);
11            postRoute();
12            return;
13        }
14        try {
15            route();
16        } catch (ZuulException e) {
17            error(e);
18            postRoute();
19            return;
20        }
21        try {
22            postRoute();
23        } catch (ZuulException e) {
24            error(e);
25            return;
26        }
27
28    } catch (Throwable e) {
29        error(new ZuulException(e, 500"UNHANDLED_EXCEPTION_" + e.getClass().getName()));
30    } finally {
31        RequestContext.getCurrentContext().unset();
32    }
33}
複製代碼

您有沒有發現,一次HTTP請求經由前置過濾器、路由過濾器、後置過濾器處理完成以後,都會調用一個方法,沒錯,就是在finally裏,RequestContext.getCurrentContext().unset()。走進RequestContext一看:

1public void unset({
2    threadLocal.remove();
3}
複製代碼

看到沒有,神器的remove又出現了。講到這裏,您是否get到ThreadLocal正確的使用"姿式"呢?

ThreadLocalMap之番外篇

筆者以前寫過關於TreeMap和HashMap的文章,凡是Map的實現,都有本身下降哈希衝突和解決哈希衝突的方法。在這裏,ThreadLocalMap是如何處理的呢?請往下看。

如何下降哈希衝突

回顧ThreadLocalMap添加元素的源碼:

  • 方式一:構造方法
1ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
2    table = new Entry[INITIAL_CAPACITY];
3    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
4    table[i] = new Entry(firstKey, firstValue);
5    size = 1;
6    setThreshold(INITIAL_CAPACITY);
7}
複製代碼
  • 方式二:set方法
 1private void set(ThreadLocal<?> key, Object value{
2
3    Entry[] tab = table;
4    int len = tab.length;
5    int i = key.threadLocalHashCode & (len-1);
6
7    for (Entry e = tab[i];
8         e != null;
9         e = tab[i = nextIndex(i, len)]) {
10        ThreadLocal<?> k = e.get();
11
12        if (k == key) {
13            e.value = value;
14            return;
15        }
16
17        if (k == null) {
18            replaceStaleEntry(key, value, i);
19            return;
20        }
21    }
22
23    tab[i] = new Entry(key, value);
24    int sz = ++size;
25    if (!cleanSomeSlots(i, sz) && sz >= threshold)
26        rehash();
27}
複製代碼

其中i就是ThreadLocal在ThreadLocalMap中存放的索引,計算方式爲:key.threadLocalHashCode & (len-1)。咱們先來看threadLocalHashCode是什麼?

1private final int threadLocalHashCode = nextHashCode();
複製代碼

也就是說,每個ThreadLocal都會根據nextHashCode生成一個int值,做爲哈希值,而後根據這個哈希值&(數組長度-1),從而獲取到哈希值的低N位(以len爲16,16-1保證低四位都是1,從而獲取哈希值自己的低四位值),從而獲取到在數組中的索引位置。那它是如何下降哈希衝突的呢?玄機就在於這個nextHashCode方法。

1private static AtomicInteger nextHashCode = new AtomicInteger();
2
3private static final int HASH_INCREMENT = 0x61c88647;
4
5private static int nextHashCode() {
6    return nextHashCode.getAndAdd(HASH_INCREMENT);
7}
複製代碼

0x61c88647是什麼?轉化爲十進制是1640531527。2654435769轉換成int類型就是-1640531527。2654435769等於(根號5-1)/2乘以2的32次方。(根號5-1)/2是什麼?是黃金分割數,近似爲0.618。也就是說0x61c88647理解爲一個黃金分割數乘以2的32次方。有什麼好處?它能夠神奇的保證nextHashCode生成的哈希值,均勻的分佈在2的冪次方上,且小於2的32次方。來看例子:

 1public class ThreadLocalHashCodeTest {
2
3    private static AtomicInteger nextHashCode =
4            new AtomicInteger();
5
6    private static final int HASH_INCREMENT = 0x61c88647;
7
8    private static int nextHashCode({
9        return nextHashCode.getAndAdd(HASH_INCREMENT);
10    }
11
12    public static void main(String[] args){
13        for (int i = 0; i < 16; i++) {
14            System.out.print(nextHashCode() & 15);
15            System.out.print(" ");
16        }
17        System.out.println();
18        for (int i = 0; i < 32; i++) {
19            System.out.print(nextHashCode() & 31);
20            System.out.print(" ");
21        }
22        System.out.println();
23        for (int i = 0; i < 64; i++) {
24            System.out.print(nextHashCode() & 63);
25            System.out.print(" ");
26        }
27    }
28}
複製代碼

輸出結果:

10 7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 
216 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0 7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 
316 23 30 37 44 51 58 1 8 15 22 29 36 43 50 57 0 7 14 21 28 35 42 49 56 63 6 13 20 27 34 41 48 55 62 5 12 19 26 33 40 47 54 61 4 11 18 25 32 39 46 53 60 3 10 17 24 31 38 45 52 59 2 9 
複製代碼

看見沒有,元素索引值完美的散列在數組當中,並無出現衝突。

如何解決哈希衝突

ThreadLocalMap採用黃金分割數的方式,大大下降了哈希衝突的狀況,可是這種狀況仍是存在的,那若是出現,它是怎麼解決的呢?請看:

 1private void set(ThreadLocal<?> key, Object value{
2
3    Entry[] tab = table;
4    int len = tab.length;
5    int i = key.threadLocalHashCode & (len-1);
6
7    // 出現哈希衝突
8    for (Entry e = tab[i];
9         e != null;
10         e = tab[i = nextIndex(i, len)]) {
11        ThreadLocal<?> k = e.get();
12
13        // 若是是同一個對象,則覆蓋value值
14        if (k == key) {
15            e.value = value;
16            return;
17        }
18
19        // 若是key爲null,則替換它的位置
20        if (k == null) {
21            replaceStaleEntry(key, value, i);
22            return;
23        }
24
25        // 不然日後一個位置找,直到找到空的位置
26    }
27
28    tab[i] = new Entry(key, value);
29    int sz = ++size;
30    if (!cleanSomeSlots(i, sz) && sz >= threshold)
31        rehash();
32}
複製代碼

當出現哈希衝突時,它的作法看是不是同一個對象或者是是否能夠替換,不然日後移動一位,繼續判斷。

1private static int nextIndex(int i, int len) {
2    return ((i + 1 < len) ? i + 1 : 0);
3}
複製代碼
擴容

經過set方法裏的代碼,咱們知道ThreadLocalMap擴容有兩個前提:

  • !cleanSomeSlots(i, sz)
  • size >= threshold

元素個數大於閾值進行擴容,這個很好理解,那麼還有一個前提是什麼意思呢?咱們來看cleanSomeSlots()作了什麼:

 1private boolean cleanSomeSlots(int i, int n) {
2    boolean removed = false;
3    Entry[] tab = table;
4    int len = tab.length;
5    do {
6        i = nextIndex(i, len);
7        Entry e = tab[i];
8        if (e != null && e.get() == null) {
9            n = len;
10            removed = true;
11            i = expungeStaleEntry(i);
12        }
13    } while ( (n >>>= 1) != 0);
14    return removed;
15}
複製代碼

方法上註釋寫的很明白,從當前插入元素位置,日後掃描數組中的元素,判斷是不是「stale entry」。在前面將ThreadLocalMap類聲明信息的時候講過,「stale entry」表示的是那些key爲null的entry。cleanSomeSlots方法就是找到他們,調用expungeStaleEntry方法進行清理。若是找到,則返回true,不然返回false。
您問:爲何擴容要看它的返回值呢?
我答:由於一旦找到,就調用expungeStaleEntry方法進行清理。

 1private int expungeStaleEntry(int staleSlot{
2            Entry[] tab = table;
3            int len = tab.length;
4
5    // expunge entry at staleSlot
6    tab[staleSlot].value = null;
7    tab[staleSlot] = null;
8    size--;
9
10    // 省略
11}
複製代碼

看到沒有,size會減一,那麼添加元素致使size加1,cleanSomeSlots一旦找到,則會清理一個或者多個元素,size減去的最少爲1,因此返回true,天然就沒有必要再判斷size是否大於等於閾值了。
好了,前提條件一旦知足,則調用rehash方法,此時還未擴容:

1private void rehash() {
2    // 先清理stale entry,會致使size變化
3    expungeStaleEntries();
4
5    // 若是size大於等於3/4閾值,則擴容
6    if (size >= threshold - threshold / 4)
7        resize();
8}
複製代碼

哈哈,這裏纔是真正的擴容,要進行擴容:

  1. 當前插入元素的位置,日後沒有須要清理的stale entry
  2. size大於等於閾值
  3. 清理掉stale entry以後,size大於等於3/4閾值

既然搞清楚了條件,那麼知足後,又是如何擴容的呢?

 1private void resize({
2    Entry[] oldTab = table;
3    int oldLen = oldTab.length;
4    int newLen = oldLen * 2;
5    // 新建一個數組,按照2倍長度擴容
6    Entry[] newTab = new Entry[newLen];
7    int count = 0;
8
9    for (int j = 0; j < oldLen; ++j) {
10        Entry e = oldTab[j];
11        if (e != null) {
12            ThreadLocal<?> k = e.get();
13            if (k == null) {
14                e.value = null// Help the GC
15            } else {
16                // key不爲null,從新計算索引位置
17                int h = k.threadLocalHashCode & (newLen - 1);
18                while (newTab[h] != null)
19                    h = nextIndex(h, newLen);
20                // 插入新的數組中索引位置
21                newTab[h] = e;
22                count++;
23            }
24        }
25    }
26
27    // 閾值爲長度的2/3
28    setThreshold(newLen);
29    size = count;
30    table = newTab;
31}
複製代碼

兩倍長度擴容,從新計算索引,擴容的同時也順便清理了key爲null的元素,即stale entry,再也不存入擴容後的數組中。

補充

不知您有沒有注意到,ThreadLocalMap中出現哈希衝突時,它是線性探測,直到找到空的位置。這種效率是很是低的,那爲何Java大神們寫代碼時還要這麼作呢?筆者認爲取決於它採用的哈希算法,正由於nextHashCode(),保證了衝突出現的可能性很低。並且ThreadLocalMap在處理過程當中很是注意清理"stale entry",及時釋放出空餘位置,從而下降了線性探測帶來的低效。

總結

本文講了這麼多,主要是爲了讓你們明白ThreadLocal應該如何正確的使用,以及使用它背後的原理。後面番外篇,純屬興趣部分,您能夠對比以前筆者《HashMap之元素插入》裏面的內容,發散思考。筆者深知水平有限,若有任何意見建議,還請您留言指出,感激涕零!!!最後,感謝你們一如既往的支持,祝近安,祁琛,2019年1月12日。

相關文章
相關標籤/搜索