ThreadLocal源碼分析

threadLocal

閱讀原文請(qǐng)?jiān)L問(wèn)我的博客BrightLoong's Blog

一. 簡(jiǎn)介

提醒篇幅較大需耐心阱佛。

簡(jiǎn)介來(lái)自ThreadLocal類注釋

ThreadLocal類提供了線程局部 (thread-local) 變量浓镜。這些變量與普通變量不同屠橄,每個(gè)線程都可以通過(guò)其 get 或 set方法來(lái)訪問(wèn)自己的獨(dú)立初始化的變量副本。ThreadLocal 實(shí)例通常是類中的 private static 字段鞍爱,它們希望將狀態(tài)與某一個(gè)線程(例如胁编,用戶 ID 或事務(wù) ID)相關(guān)聯(lián)竟痰。

下面是類注釋中給出的一個(gè)列子:

以下類生成對(duì)每個(gè)線程唯一的局部標(biāo)識(shí)符。 線程 ID 是在第一次調(diào)用 UniqueThreadIdGenerator.getCurrentThreadId() 時(shí)分配的掏呼,在后續(xù)調(diào)用中不會(huì)更改。

import java.util.concurrent.atomic.AtomicInteger;
  
    public class ThreadId {
    // 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();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                public void run() {
                    System.out.print(threadId.get());
                }
            }).start();
        }
    }
}

輸出結(jié)果 :01234

只要線程是活動(dòng)的并且 ThreadLocal 實(shí)例是可訪問(wèn)的铅檩,每個(gè)線程都會(huì)保持對(duì)其線程局部變量副本的隱式引用憎夷;在線程消失之后,其線程局部實(shí)例的所有副本都會(huì)被垃圾回收(除非存在對(duì)這些副本的其他引用)昧旨。

二. 整體認(rèn)識(shí)

UML類圖

threadLocal UML

ThreadLocal中的嵌套內(nèi)部類ThreadLocalMap拾给,這個(gè)類本質(zhì)上是一個(gè)map,和HashMap之類的實(shí)現(xiàn)相似兔沃,依然是key-value的形式蒋得,其中有一個(gè)內(nèi)部類Entry,其中key可以看做是ThreadLocal實(shí)例乒疏,但是其本質(zhì)是持有ThreadLocal實(shí)例的弱引用(之后會(huì)詳細(xì)說(shuō)到)额衙。

還是說(shuō)ThreadLocalMap(下面是很大篇幅的閱讀其源碼,畢竟了解清楚了ThreadLocalMap的來(lái)龍去脈,ThreadLocal基本也就差不多了)窍侧,在ThreadLocal中并沒(méi)有對(duì)于ThreadLocalMap的引用县踢,是的,ThreadLocalMap的引用在Thread類中伟件,代碼如下硼啤。每個(gè)線程在向ThreadLocal里塞值的時(shí)候,其實(shí)都是向自己所持有的ThreadLocalMap里塞入數(shù)據(jù)斧账;讀的時(shí)候同理谴返,首先從自己線程中取出自己持有的ThreadLocalMap,然后再根據(jù)ThreadLocal引用作為key取出value咧织,基于以上描述嗓袱,ThreadLocal實(shí)現(xiàn)了變量的線程隔離(當(dāng)然,畢竟變量其實(shí)都是從自己當(dāng)前線程實(shí)例中取出來(lái)的)拯爽。

public
class Thread implements Runnable {
    ......
    
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    ......

原理圖

根據(jù)理解索抓,畫出ThreadLocal原理圖如下:


原理圖
  • 首先,主線程定義的兩個(gè)ThreadLocal變量毯炮,和兩個(gè)子線程——線程A和線程B逼肯。
  • 線程A和線程B分別持有一個(gè)ThreadLocalMap用于保存自己獨(dú)立的副本,主線程的ThreadLocal中封裝了get()和set()之類的方法桃煎。
  • 在線程A和線程B中調(diào)用ThreadLocal的set方法篮幢,會(huì)首先通過(guò)getMap(Thread.currentThread)獲得線程A或者是線程B持有的ThreadLocalMap,在調(diào)用map.put()方法,并將ThreadLocal作為key为迈。
  • get()方法和set()方法原理類似三椿,也是先獲取當(dāng)前調(diào)用線程的ThreadLocalMap,再?gòu)膍ap中獲取value,并將ThreadLocal作為key葫辐。

三. ThreadLocalMap源碼分析

下面一步一步介紹ThreadLocalMap源碼分析的相關(guān)源碼搜锰,在分析ThreadLocalMap的同時(shí),也會(huì)介紹與ThreadLocalMap關(guān)聯(lián)的ThreadLocal中的方法(這樣分析完ThreadLocalMap耿战,ThreadLocal基本就搞定了)蛋叼,可能有些需要前后結(jié)合才能真正理解肩杈。

成員變量

    /**
     * 初始容量 —— 必須是2的冥
     */
    private static final int INITIAL_CAPACITY = 16;

    /**
     * 存放數(shù)據(jù)的table秀鞭,Entry類的定義在下面分析
     * 同樣,數(shù)組長(zhǎng)度必須是2的冥涯鲁。
     */
    private Entry[] table;

    /**
     * 數(shù)組里面entrys的個(gè)數(shù)鸭栖,可以用于判斷table當(dāng)前使用量是否超過(guò)負(fù)因子歌馍。
     */
    private int size = 0;

    /**
     * 進(jìn)行擴(kuò)容的閾值,表使用量大于它的時(shí)候進(jìn)行擴(kuò)容晕鹊。
     */
    private int threshold; // Default to 0
    
    /**
     * 定義為長(zhǎng)度的2/3
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

各個(gè)值的含義已經(jīng)在注釋里面說(shuō)了松却,就不再一一解釋暴浦。

存儲(chǔ)結(jié)構(gòu)——Entry

/**
 * Entry繼承WeakReference,并且用ThreadLocal作為key.如果key為null
 * (entry.get() == null)表示key不再被引用玻褪,表示ThreadLocal對(duì)象被回收
 * 因此這時(shí)候entry也可以從table從清除肉渴。
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

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

Entry繼承WeakReference,使用弱引用,可以將ThreadLocal對(duì)象的生命周期和線程生命周期解綁带射,持有對(duì)ThreadLocal的弱引用同规,可以使得ThreadLocal在沒(méi)有其他強(qiáng)引用的時(shí)候被回收掉,這樣可以避免因?yàn)榫€程得不到銷毀導(dǎo)致ThreadLocal對(duì)象無(wú)法被回收窟社。

關(guān)于WeakReference可以參考我之前的博客券勺,關(guān)于Java中的WeakReferencea

ThreadLocalMap的set()方法和Hash映射

要了解ThreadLocalMap中Hash映射的方式灿里,首先從ThreadLocal的set()方法入手关炼,逐層深入。

ThreadLocal中的set()

先看看ThreadLocal中set()源碼匣吊。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocal.ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }
  • 代碼很簡(jiǎn)單儒拂,獲取當(dāng)前線程,并獲取當(dāng)前線程的ThreadLocalMap實(shí)例(從getMap(Thread t)中很容易看出來(lái))色鸳。
  • 如果獲取到的map實(shí)例不為空社痛,調(diào)用map.set()方法,否則調(diào)用構(gòu)造函數(shù) ThreadLocal.ThreadLocalMap(this, firstValue)實(shí)例化map命雀。

可以看出來(lái)線程中的ThreadLocalMap使用的是延遲初始化蒜哀,在第一次調(diào)用get()或者set()方法的時(shí)候才會(huì)進(jìn)行初始化。下面來(lái)看看構(gòu)造函數(shù)ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) 吏砂。

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        //初始化table
        table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
        //計(jì)算索引
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        //設(shè)置值
        table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
        size = 1;
        //設(shè)置閾值
        setThreshold(INITIAL_CAPACITY);
    }

主要說(shuō)一下計(jì)算索引撵儿,firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)

  • 關(guān)于& (INITIAL_CAPACITY - 1),這是取模的一種方式狐血,對(duì)于2的冪作為模數(shù)取模淀歇,用此代替%(2^n),這也就是為啥容量必須為2的冥匈织,在這個(gè)地方也得到了解答房匆,至于為什么可以這樣這里不過(guò)多解釋,原理很簡(jiǎn)單报亩。
  • 關(guān)于firstKey.threadLocalHashCode
    private final int threadLocalHashCode = nextHashCode();
    
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    private static AtomicInteger nextHashCode =
            new AtomicInteger();
            
    private static final int HASH_INCREMENT = 0x61c88647;
    

定義了一個(gè)AtomicInteger類型,每次獲取當(dāng)前值并加上HASH_INCREMENT井氢,HASH_INCREMENT = 0x61c88647,關(guān)于這個(gè)值和斐波那契散列有關(guān)弦追,其原理這里不再深究,感興趣可自行搜索花竞,其主要目的就是為了讓哈希碼能均勻的分布在2的n次方的數(shù)組里, 也就是Entry[] table中劲件。

在了解了上面的源碼后掸哑,終于能進(jìn)入正題了,下面開(kāi)始進(jìn)入ThreadLocalMap中的set()零远。

ThreadLocalMap中的set()

ThreadLocalMap使用線性探測(cè)法來(lái)解決哈希沖突苗分,線性探測(cè)法的地址增量di = 1, 2, ... , m-1,其中牵辣,i為探測(cè)次數(shù)摔癣。該方法一次探測(cè)下一個(gè)地址,直到有空的地址后插入纬向,若整個(gè)空間都找不到空余的地址择浊,則產(chǎn)生溢出。假設(shè)當(dāng)前table長(zhǎng)度為16逾条,也就是說(shuō)如果計(jì)算出來(lái)key的hash值為14琢岩,如果table[14]上已經(jīng)有值,并且其key與當(dāng)前key不一致师脂,那么就發(fā)生了hash沖突担孔,這個(gè)時(shí)候?qū)?4加1得到15,取table[15]進(jìn)行判斷吃警,這個(gè)時(shí)候如果還是沖突會(huì)回到0糕篇,取table[0],以此類推,直到可以插入汤徽。

按照上面的描述娩缰,可以把table看成一個(gè)環(huán)形數(shù)組

先看一下線性探測(cè)相關(guān)的代碼谒府,從中也可以看出來(lái)table實(shí)際是一個(gè)環(huán):

/**java
    /**
     * 獲取環(huán)形數(shù)組的下一個(gè)索引
     */
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    /**
     * 獲取環(huán)形數(shù)組的上一個(gè)索引
     */
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }

ThreadLocalMap的set()及其set()相關(guān)代碼如下:

    private void set(ThreadLocal<?> key, Object value) {
        ThreadLocal.ThreadLocalMap.Entry[] tab = table;
        int len = tab.length;
        //計(jì)算索引拼坎,上面已經(jīng)有說(shuō)過(guò)。
        int i = key.threadLocalHashCode & (len-1);

        /**
         * 根據(jù)獲取到的索引進(jìn)行循環(huán)完疫,如果當(dāng)前索引上的table[i]不為空泰鸡,在沒(méi)有return的情況下,
         * 就使用nextIndex()獲取下一個(gè)(上面提到到線性探測(cè)法)壳鹤。
         */
        for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            //table[i]上key不為空盛龄,并且和當(dāng)前key相同,更新value
            if (k == key) {
                e.value = value;
                return;
            }
            /**
             * table[i]上的key為空芳誓,說(shuō)明被回收了(上面的弱引用中提到過(guò))余舶。
             * 這個(gè)時(shí)候說(shuō)明改table[i]可以重新使用,用新的key-value將其替換,并刪除其他無(wú)效的entry
             */
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }

        //找到為空的插入位置锹淌,插入值匿值,在為空的位置插入需要對(duì)size進(jìn)行加1操作
        tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
        int sz = ++size;

        /**
         * cleanSomeSlots用于清除那些e.get()==null,也就是table[index] != null && table[index].get()==null
         * 之前提到過(guò)赂摆,這種數(shù)據(jù)key關(guān)聯(lián)的對(duì)象已經(jīng)被回收挟憔,所以這個(gè)Entry(table[index])可以被置null钟些。
         * 如果沒(méi)有清除任何entry,并且當(dāng)前使用量達(dá)到了負(fù)載因子所定義(長(zhǎng)度的2/3),那么進(jìn)行rehash()
         */
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }


    /**
     * 替換無(wú)效entry
     */
    private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                   int staleSlot) {
        ThreadLocal.ThreadLocalMap.Entry[] tab = table;
        int len = tab.length;
        ThreadLocal.ThreadLocalMap.Entry e;

        /**
         * 根據(jù)傳入的無(wú)效entry的位置(staleSlot),向前掃描
         * 一段連續(xù)的entry(這里的連續(xù)是指一段相鄰的entry并且table[i] != null),
         * 直到找到一個(gè)無(wú)效entry绊谭,或者掃描完也沒(méi)找到
         */
        int slotToExpunge = staleSlot;//之后用于清理的起點(diǎn)
        for (int i = prevIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = prevIndex(i, len))
            if (e.get() == null)
                slotToExpunge = i;

        /**
         * 向后掃描一段連續(xù)的entry
         */
        for (int i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();

            /**
             * 如果找到了key政恍,將其與傳入的無(wú)效entry替換,也就是與table[staleSlot]進(jìn)行替換
             */
            if (k == key) {
                e.value = value;

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

                //如果向前查找沒(méi)有找到無(wú)效entry达传,則更新slotToExpunge為當(dāng)前值i
                if (slotToExpunge == staleSlot)
                    slotToExpunge = i;
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                return;
            }

            /**
             * 如果向前查找沒(méi)有找到無(wú)效entry篙耗,并且當(dāng)前向后掃描的entry無(wú)效,則更新slotToExpunge為當(dāng)前值i
             */
            if (k == null && slotToExpunge == staleSlot)
                slotToExpunge = i;
        }

        /**
         * 如果沒(méi)有找到key,也就是說(shuō)key之前不存在table中
         * 就直接最開(kāi)始的無(wú)效entry——tab[staleSlot]上直接新增即可
         */
        tab[staleSlot].value = null;
        tab[staleSlot] = new ThreadLocal.ThreadLocalMap.Entry(key, value);

        /**
         * slotToExpunge != staleSlot,說(shuō)明存在其他的無(wú)效entry需要進(jìn)行清理趟大。
         */
        if (slotToExpunge != staleSlot)
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    }

    /**
     * 連續(xù)段清除
     * 根據(jù)傳入的staleSlot,清理對(duì)應(yīng)的無(wú)效entry——table[staleSlot],
     * 并且根據(jù)當(dāng)前傳入的staleSlot,向后掃描一段連續(xù)的entry(這里的連續(xù)是指一段相鄰的entry并且table[i] != null),
     * 對(duì)可能存在hash沖突的entry進(jìn)行rehash鹤树,并且清理遇到的無(wú)效entry.
     *
     * @param staleSlot key為null,需要無(wú)效entry所在的table中的索引
     * @return 返回下一個(gè)為空的solt的索引。
     */
    private int expungeStaleEntry(int staleSlot) {
        ThreadLocal.ThreadLocalMap.Entry[] tab = table;
        int len = tab.length;

        // 清理無(wú)效entry逊朽,置空
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        //size減1罕伯,置空后table的被使用量減1
        size--;

        ThreadLocal.ThreadLocalMap.Entry e;
        int i;
        /**
         * 從staleSlot開(kāi)始向后掃描一段連續(xù)的entry
         */
        for (i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            //如果遇到key為null,表示無(wú)效entry,進(jìn)行清理.
            if (k == null) {
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                //如果key不為null,計(jì)算索引
                int h = k.threadLocalHashCode & (len - 1);
                /**
                 * 計(jì)算出來(lái)的索引——h叽讳,與其現(xiàn)在所在位置的索引——i不一致追他,置空當(dāng)前的table[i]
                 * 從h開(kāi)始向后線性探測(cè)到第一個(gè)空的slot,把當(dāng)前的entry挪過(guò)去岛蚤。
                 */
                if (h != i) {
                    tab[i] = null;
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    tab[h] = e;
                }
            }
        }
        //下一個(gè)為空的solt的索引邑狸。
        return i;
    }

    /**
     * 啟發(fā)式的掃描清除,掃描次數(shù)由傳入的參數(shù)n決定
     *
     * @param i 從i向后開(kāi)始掃描(不包括i涤妒,因?yàn)樗饕秊閕的Slot肯定為null)
     *
     * @param n 控制掃描次數(shù)单雾,正常情況下為 log2(n) ,
     * 如果找到了無(wú)效entry她紫,會(huì)將n重置為table的長(zhǎng)度len,進(jìn)行段清除硅堆。
     *
     * map.set()點(diǎn)用的時(shí)候傳入的是元素個(gè)數(shù),replaceStaleEntry()調(diào)用的時(shí)候傳入的是table的長(zhǎng)度len
     *
     * @return true if any stale entries have been removed.
     */
    private boolean cleanSomeSlots(int i, int n) {
        boolean removed = false;
        ThreadLocal.ThreadLocalMap.Entry[] tab = table;
        int len = tab.length;
        do {
            i = nextIndex(i, len);
            ThreadLocal.ThreadLocalMap.Entry e = tab[i];
            if (e != null && e.get() == null) {
                //重置n為len
                n = len;
                removed = true;
                //依然調(diào)用expungeStaleEntry來(lái)進(jìn)行無(wú)效entry的清除
                i = expungeStaleEntry(i);
            }
        } while ( (n >>>= 1) != 0);//無(wú)符號(hào)的右移動(dòng)贿讹,可以用于控制掃描次數(shù)在log2(n)
        return removed;
    }


    private void rehash() {
        //全清理
        expungeStaleEntries();

        /**
         * threshold = 2/3 * len
         * 所以threshold - threshold / 4 = 1en/2
         * 這里主要是因?yàn)樯厦孀隽艘淮稳謇硭詓ize減小渐逃,需要進(jìn)行判斷。
         * 判斷的時(shí)候把閾值調(diào)低了民褂。
         */
        if (size >= threshold - threshold / 4)
            resize();
    }

    /**
     * 擴(kuò)容茄菊,擴(kuò)大為原來(lái)的2倍(這樣保證了長(zhǎng)度為2的冥)
     */
    private void resize() {
        ThreadLocal.ThreadLocalMap.Entry[] oldTab = table;
        int oldLen = oldTab.length;
        int newLen = oldLen * 2;
        ThreadLocal.ThreadLocalMap.Entry[] newTab = new ThreadLocal.ThreadLocalMap.Entry[newLen];
        int count = 0;

        for (int j = 0; j < oldLen; ++j) {
            ThreadLocal.ThreadLocalMap.Entry e = oldTab[j];
            if (e != null) {
                ThreadLocal<?> k = e.get();
                //雖然做過(guò)一次清理,但在擴(kuò)容的時(shí)候可能會(huì)又存在key==null的情況赊堪。
                if (k == null) {
                    //這里試試將e.value設(shè)置為null
                    e.value = null; // Help the GC
                } else {
                    //同樣適用線性探測(cè)來(lái)設(shè)置值面殖。
                    int h = k.threadLocalHashCode & (newLen - 1);
                    while (newTab[h] != null)
                        h = nextIndex(h, newLen);
                    newTab[h] = e;
                    count++;
                }
            }
        }

        //設(shè)置新的閾值
        setThreshold(newLen);
        size = count;
        table = newTab;
    }

    /**
     * 全清理,清理所有無(wú)效entry
     */
    private void expungeStaleEntries() {
        ThreadLocal.ThreadLocalMap.Entry[] tab = table;
        int len = tab.length;
        for (int j = 0; j < len; j++) {
            ThreadLocal.ThreadLocalMap.Entry e = tab[j];
            if (e != null && e.get() == null)
                //使用連續(xù)段清理
                expungeStaleEntry(j);
        }
    }

ThreadLocalMap中的getEntry()及其相關(guān)

同樣的對(duì)于ThreadLocalMap中的getEntry()也從ThreadLocal的get()方法入手哭廉。

ThreadLocal中的get()
public T get() {
    //同set方法類似獲取對(duì)應(yīng)線程中的ThreadLocalMap實(shí)例
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //為空返回初始化值
    return setInitialValue();
}
/**
 * 初始化設(shè)值的方法脊僚,可以被子類覆蓋。
 */
protected T initialValue() {
   return null;
}

private T setInitialValue() {
    //獲取初始化值群叶,默認(rèn)為null(如果沒(méi)有子類進(jìn)行覆蓋)
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //不為空不用再初始化吃挑,直接調(diào)用set操作設(shè)值
    if (map != null)
        map.set(this, value);
    else
        //第一次初始化,createMap在上面介紹set()的時(shí)候有介紹過(guò)街立。
        createMap(t, value);
    return value;
}
ThreadLocalMap中的getEntry()
    private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) {
        //根據(jù)key計(jì)算索引舶衬,獲取entry
        int i = key.threadLocalHashCode & (table.length - 1);
        ThreadLocal.ThreadLocalMap.Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

    /**
     * 通過(guò)直接計(jì)算出來(lái)的key找不到對(duì)于的value的時(shí)候適用這個(gè)方法.
     */
    private ThreadLocal.ThreadLocalMap.Entry getEntryAfterMiss(ThreadLocal<?> key, int i, ThreadLocal.ThreadLocalMap.Entry e) {
        ThreadLocal.ThreadLocalMap.Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)
                return e;
            if (k == null)
                //清除無(wú)效的entry
                expungeStaleEntry(i);
            else
                //基于線性探測(cè)法向后掃描
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }

ThreadLocalMap中的remove()

    private void remove(ThreadLocal<?> key) {
        ThreadLocal.ThreadLocalMap.Entry[] tab = table;
        int len = tab.length;
        //計(jì)算索引
        int i = key.threadLocalHashCode & (len-1);
        //進(jìn)行線性探測(cè),查找正確的key
        for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            if (e.get() == key) {
                //調(diào)用weakrefrence的clear()清除引用
                e.clear();
                //連續(xù)段清除
                expungeStaleEntry(i);
                return;
            }
        }
    }

remove()在有上面了解后可以說(shuō)極為簡(jiǎn)單了赎离,就是找到對(duì)應(yīng)的table[],調(diào)用weakrefrence的clear()清除引用逛犹,然后再調(diào)用expungeStaleEntry()進(jìn)行清除。

四. 總結(jié)

分析完ThreadLocalMap梁剔,ThreadLocal的神秘面紗也就揭開(kāi)了虽画,所以不再贅述。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荣病,一起剝皮案震驚了整個(gè)濱河市码撰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌个盆,老刑警劉巖脖岛,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異颊亮,居然都是意外死亡柴梆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門终惑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)绍在,“玉大人,你說(shuō)我怎么就攤上這事雹有〕ザ桑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵件舵,是天一觀的道長(zhǎng)卸察。 經(jīng)常有香客問(wèn)我,道長(zhǎng)铅祸,這世上最難降的妖魔是什么坑质? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮临梗,結(jié)果婚禮上涡扼,老公的妹妹穿的比我還像新娘。我一直安慰自己盟庞,他們只是感情好吃沪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著什猖,像睡著了一般票彪。 火紅的嫁衣襯著肌膚如雪红淡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天降铸,我揣著相機(jī)與錄音在旱,去河邊找鬼。 笑死推掸,一個(gè)胖子當(dāng)著我的面吹牛桶蝎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谅畅,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼登渣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了毡泻?” 一聲冷哼從身側(cè)響起胜茧,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牙捉,沒(méi)想到半個(gè)月后竹揍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邪铲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年芬位,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片带到。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昧碉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出揽惹,到底是詐尸還是另有隱情被饿,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布搪搏,位于F島的核電站狭握,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疯溺。R本人自食惡果不足惜论颅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囱嫩。 院中可真熱鬧恃疯,春花似錦、人聲如沸墨闲。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至盾鳞,卻和暖如春犬性,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腾仅。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工仔夺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人攒砖。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像日裙,于是被迫代替她去往敵國(guó)和親吹艇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • ThreadLocal昂拂,線程變量受神,是一個(gè)以ThreadLocal對(duì)象為鍵,任意對(duì)象為值 的存儲(chǔ) 結(jié)構(gòu)格侯。該結(jié)構(gòu)附著于...
    Justlearn閱讀 407評(píng)論 0 2
  • ThreadLocal和線程同步機(jī)制相比:都是為了解決多線程中相同變量的訪問(wèn)沖突問(wèn)題鼻听。在同步機(jī)制中,通過(guò)對(duì)象的鎖機(jī)...
    tiancijiaren閱讀 381評(píng)論 0 1
  • 首先通過(guò)問(wèn)題去看源碼 ThreadLocal通過(guò)空間換取線程變量安全的說(shuō)法正確嗎 ThreadLocal為什么說(shuō)會(huì)...
    Visualing閱讀 238評(píng)論 0 2
  • 前言 ThreadLocal很多同學(xué)都搞不懂是什么東西联四,可以用來(lái)干嘛撑碴。但面試時(shí)卻又經(jīng)常問(wèn)到,所以這次我和大家一起學(xué)...
    liangzzz閱讀 12,452評(píng)論 14 228
  • 小雨是我曾經(jīng)在網(wǎng)上遇到的一個(gè)朋友朝墩。 我們斷斷續(xù)續(xù)的聊了四五年醉拓,沒(méi)有發(fā)過(guò)照片,沒(méi)有語(yǔ)音視頻收苏。在這個(gè)年代亿卤,總顯得有些珍...
    失你失心閱讀 482評(píng)論 0 1