Netty_ThreadLocal和FastThreadLocal詳解

在平常開發(fā)的時(shí)候,經(jīng)常使用到線程本地變量,這種類型的變量會(huì)在每個(gè)線程中都有一份,互相不會(huì)產(chǎn)生影響,這樣來解決多線程并發(fā)問題。
那么是如何實(shí)現(xiàn)的呢?

一. ThreadLocal<T>

1.1 例子

   private static final ThreadLocal<AtomicInteger> threadLocal = new ThreadLocal<AtomicInteger>(){
        @Override
        protected AtomicInteger initialValue() {
            AtomicInteger result = new AtomicInteger(0);
            System.out.println("創(chuàng)建 AtomicInteger("+result.hashCode()+")   thread:"+Thread.currentThread().getName());
            return result;
        }
    };

    public static void main(String[] args) {

        for (int index = 0; index < 2; index++) {
            new Thread(() -> {
               for (int i = 0; i < 5; i++) {
                   System.out.println(threadLocal.get().incrementAndGet()
                           +"   thread:"+Thread.currentThread().getName());
               }
            }).start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

運(yùn)行結(jié)果

創(chuàng)建 AtomicInteger(952204834)   thread:Thread-0
創(chuàng)建 AtomicInteger(471787139)   thread:Thread-1
1   thread:Thread-0
2   thread:Thread-0
3   thread:Thread-0
4   thread:Thread-0
1   thread:Thread-1
5   thread:Thread-0
2   thread:Thread-1
3   thread:Thread-1
4   thread:Thread-1
5   thread:Thread-1

從運(yùn)行結(jié)果可以得出每個(gè)線程都創(chuàng)建了AtomicInteger 實(shí)例,因此彼此不會(huì)產(chǎn)生影響。

ThreadLocal<T> 可以看出兩部分:

  • 一個(gè)是ThreadLocal 對(duì)象實(shí)例(即例子中的 threadLocal),這個(gè)實(shí)例只有一個(gè),多線程共享的。
  • 另一個(gè)是由ThreadLocal 對(duì)象實(shí)例創(chuàng)建的對(duì)象(即例子中的AtomicInteger),這個(gè)是每個(gè)線程都會(huì)創(chuàng)建并持有。

因此你會(huì)發(fā)現(xiàn):

  • 每個(gè)線程可以根據(jù)ThreadLocal 對(duì)象實(shí)例threadLocal來查找對(duì)應(yīng)的所創(chuàng)建的對(duì)象AtomicInteger,相當(dāng)于key->value 的鍵值映射關(guān)系。
  • 而每個(gè)線程可以有多個(gè)ThreadLocal 對(duì)象實(shí)例,即多個(gè)key。
  • 那么我們可以斷定,每個(gè)線程肯定有一個(gè)集合對(duì)象來存儲(chǔ)上面的多個(gè)key->value 鍵值映射關(guān)系,其實(shí)就是 Thread 中成員屬性 ThreadLocal.ThreadLocalMap threadLocals。

1.2 get 方法

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

    public T get() {
        // 先獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // 從當(dāng)前線程中獲取存儲(chǔ)鍵值映射關(guān)系的Map
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 如果這個(gè)Map存在,那么直接從里面獲取映射關(guān)系e
            ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                // 映射關(guān)系e 存在,那么直接獲取創(chuàng)建的對(duì)象
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 程序走到這里,說明當(dāng)前線程 這個(gè)ThreadLocal 實(shí)例對(duì)應(yīng)對(duì)象還沒有創(chuàng)建,
        // 那么就進(jìn)行初始化創(chuàng)建
        return setInitialValue();
    }

從當(dāng)前線程存儲(chǔ)的映射關(guān)系集合 threadLocals 中,查找當(dāng)前這個(gè)ThreadLocal 對(duì)象實(shí)例所對(duì)應(yīng)的對(duì)象是否存在;存在就返回,不存在就setInitialValue() 方法進(jìn)行創(chuàng)建。

1.3 setInitialValue() 方法

    private T setInitialValue() {
        // 調(diào)用 initialValue() 方法得到初始化值
        T value = initialValue();
        // 先獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // 從當(dāng)前線程中獲取存儲(chǔ)鍵值映射關(guān)系的Map
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
            // 存儲(chǔ) key-value 的映射關(guān)系
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

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

1.4 ThreadLocalMap

ThreadLocalMap也是用一個(gè)哈希表數(shù)據(jù)結(jié)構(gòu)來儲(chǔ)存key-value 的映射關(guān)系,只不過它不是用鏈地址法來解決哈希沖突,而是用開放地址法的 線性探測來解決哈希沖突。

關(guān)于哈希表,以及鏈地址法和開放地址法的原理,在我的這篇文章哈希表 中有全面的介紹。

1.5 小結(jié)

ThreadLocal.png

從圖中我們就可以知道,每個(gè)線程中都一個(gè)threadLocals 屬性,它的類型是 ThreadLocalMap, 這個(gè) ThreadLocalMap 會(huì)記錄當(dāng)前線程所有產(chǎn)生的 ThreadLocal 對(duì)象。

二. FastThreadLocal<V>

 private static final FastThreadLocal<AtomicInteger> fastThreadLocal = new FastThreadLocal<AtomicInteger>(){
        @Override
        protected AtomicInteger initialValue() {
            AtomicInteger result = new AtomicInteger(0);
            System.out.println("創(chuàng)建 AtomicInteger("+result.hashCode()+")   thread:"+Thread.currentThread().getName());
            return result;
        }
    };

    public static void main(String[] args) {
        for (int index = 0; index < 2; index++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(fastThreadLocal.get().incrementAndGet()
                                +"   thread:"+Thread.currentThread().getName());
                    }
                }
            }).start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

運(yùn)行結(jié)果

創(chuàng)建 AtomicInteger(125165235)   thread:Thread-1
創(chuàng)建 AtomicInteger(1223947416)   thread:Thread-0
1   thread:Thread-1
1   thread:Thread-0
2   thread:Thread-1
2   thread:Thread-0
3   thread:Thread-1
3   thread:Thread-0
4   thread:Thread-1
5   thread:Thread-1
4   thread:Thread-0
5   thread:Thread-0

從運(yùn)行結(jié)果來看,FastThreadLocal<V>ThreadLocal<T> 效果是一樣的,那么 FastThreadLocal<V> 的優(yōu)點(diǎn)在哪里呢?

從上面的介紹中,我們知道 ThreadLocal<T> 通過哈希表來儲(chǔ)存數(shù)據(jù),從哈希表查找數(shù)據(jù)的過程如下:

  • 根據(jù) ThreadLocal<T> 實(shí)例對(duì)象threadLocal的哈希值,得到對(duì)應(yīng)數(shù)組下標(biāo)。
  • 再比較這個(gè)數(shù)組下標(biāo)存儲(chǔ)的映射關(guān)系entrykey 和實(shí)例對(duì)象threadLocal是否相等,相等的話,就直接返回entryvalue值。
  • 如果不等,那么就要進(jìn)行線性探測,查找下一個(gè)下標(biāo)的映射關(guān)系entry,是否符合要求,知道找到映射關(guān)系entrykey和當(dāng)前實(shí)例對(duì)象threadLocal相等。
  • 所以對(duì)于這種開發(fā)地址法的哈希表,極個(gè)別情況下,查找過程可能會(huì)耗時(shí),要進(jìn)行多次線性探測。

那么 FastThreadLocal<V> 就采用了空間換時(shí)間的方式加快查找速度。

  • ThreadLocal<T> VS FastThreadLocal<V>
    • ThreadLocal<T> 有一個(gè)threadLocalHashCode 屬性,在創(chuàng)建的時(shí)候被賦值,而且是不可變的屬性,代表當(dāng)前這個(gè) ThreadLocal<T>實(shí)例對(duì)象的哈希值,用來在哈希表ThreadLocalMap中查找對(duì)應(yīng)的映射關(guān)系。
    • FastThreadLocal<V> 有一個(gè) index 屬性,在創(chuàng)建的時(shí)候被賦值,而且是不可變的屬性,這個(gè)值就代表當(dāng)前FastThreadLocal<V>實(shí)例對(duì)象在 InternalThreadLocalMap 實(shí)例的 indexedVariables 的下標(biāo),通過這個(gè)下標(biāo)得到FastThreadLocal<V>所創(chuàng)建的當(dāng)前線程對(duì)象。
  • ThreadLocalMapInternalThreadLocalMap
    • ThreadLocalMap 是一個(gè)哈希表,用來儲(chǔ)存ThreadLocal<T>實(shí)例對(duì)象和它所創(chuàng)建的當(dāng)前線程對(duì)象的映射關(guān)系,就可以通過ThreadLocal<T>實(shí)例對(duì)象查找它所創(chuàng)建的當(dāng)前線程對(duì)象。
    • InternalThreadLocalMap 就是一個(gè)數(shù)組,用來存儲(chǔ)FastThreadLocal<V>實(shí)例對(duì)象所創(chuàng)建的當(dāng)前線程對(duì)象,不過存儲(chǔ)這個(gè)值的數(shù)組下標(biāo)就是FastThreadLocal<V>實(shí)例對(duì)象的index 屬性值。
    • InternalThreadLocalMap 數(shù)組下標(biāo)0 這個(gè)位置比較特殊,0 下標(biāo)存儲(chǔ)當(dāng)前線程所有的 FastThreadLocal<V> 對(duì)象實(shí)例,用于當(dāng)前線程銷毀時(shí),移除當(dāng)前線程所有的 FastThreadLocal<V> 對(duì)象實(shí)例所創(chuàng)建的當(dāng)前線程對(duì)象。
FastThreadLocal.png

需要注意的點(diǎn):

  • 每個(gè)FastThreadLocal 再創(chuàng)建的時(shí)候,index 屬性就被賦值了,也就是說這個(gè) FastThreadLocal 實(shí)例,在每個(gè)線程獲取的 InternalThreadLocalMap 中的下標(biāo)是一樣的,都是 index。

    這就導(dǎo)致一個(gè)嚴(yán)重問題,如果 FastThreadLocal 實(shí)例較多的話,某一個(gè)線程用到了 index 較大FastThreadLocal 實(shí)例的話,它必須創(chuàng)建一個(gè)很大的數(shù)組,這樣才能在FastThreadLocal 實(shí)例對(duì)應(yīng)下標(biāo) index 中儲(chǔ)存FastThreadLocal 實(shí)例創(chuàng)建的對(duì)象。

  • ThreadLocal 沒有這個(gè)問題,雖然它的哈希值也是創(chuàng)建的時(shí)候就確定了,但是它通過 哈希的方法尋找數(shù)組下標(biāo),那么當(dāng)前線程中ThreadLocalMap 的數(shù)組長度只會(huì)和當(dāng)前線程擁有的ThreadLocal 實(shí)例有關(guān)。

    這個(gè)的問題就是通過哈希查找,效率有點(diǎn)影響。

  • InternalThreadLocalMap0 下標(biāo)做了特殊處理,用來存放每個(gè)線程擁有的 FastThreadLocal 實(shí)例集合,當(dāng)線程退出時(shí),清理這些 FastThreadLocal 實(shí)例為當(dāng)前線程中產(chǎn)生的對(duì)象。
  • ThreadLocalMap 沒有做這方面的處理,那是因?yàn)?ThreadLocalMap 中使用 WeakReference<ThreadLocal<?>> 來記錄 ThreadLocal<?> 實(shí)例的。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容