ThreadLocal與FastThreadLocal

java.lang.ThreadLocal

ThreadLocal類通過(guò)線程封閉的方式解決線程安全,提到它,大家都會(huì)想到弱引用和內(nèi)存泄漏等話題,它的get/set/remove等方法,網(wǎng)上有很多關(guān)于它的話題和詳細(xì)介紹.這篇文章不會(huì)介紹這些內(nèi)容.

ThreadLocal作為key被存放到線程的ThreadLocal.ThreadLocalMap中.我們簡(jiǎn)單看下它的set方法.

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // this就是ThreadLocal,它作為key
        map.set(this, value);
    else
        createMap(t, value);
}
private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    // 這里的key就是ThreadLocal對(duì)象,根據(jù)它的threadLocalHashCode屬性值進(jìn)行取模計(jì)算,得到它應(yīng)該所在的下標(biāo)值是多少
    int i = key.threadLocalHashCode & (len-1);


    for (Entry e = tab[i];
         e != null;
         // 如果所在的下標(biāo)已經(jīng)有值了,則根據(jù)線性探測(cè)繼續(xù)計(jì)算下一個(gè)下標(biāo)值
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        ...
    }
    ...
}
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

根據(jù)以上的分析,ThreadLocal對(duì)象作為T(mén)hreadLocal.ThreadLocalMap中的key存放在Map中.(Map的底層是基于數(shù)組)
具體是根據(jù)ThreadLocal對(duì)象的threadLocalHashCode屬性值進(jìn)行取模計(jì)算出它在數(shù)組中的下標(biāo),假如ThreadLocal的threadLocalHashCode=1253254570,數(shù)組的默認(rèn)長(zhǎng)度是16,因此threadLocalHashCode & (16-1)=10,因此這個(gè)ThreadLocal對(duì)象應(yīng)該放在數(shù)組的第10個(gè)位置.如果第10個(gè)位置已經(jīng)有別的ThreadLocal對(duì)象霸占了,那么它就會(huì)嘗試第11個(gè)位置是否有空缺,以此類推,直到找到一個(gè)空缺位置.

開(kāi)放定址法中的線性探測(cè)

舉例如下

import io.netty.util.concurrent.FastThreadLocal;

public class Address {
    // 定義了兩個(gè)ThreadLocal對(duì)象
    public static final ThreadLocal<String> COMPANY = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "CHINA";
        }
    };

    public static final ThreadLocal<Integer> YEAR = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 1949;
        }
    };
}
Thread t1 = new Thread(() -> {
    System.out.println(Address.COMPANY.get());
    System.out.println(Address.YEAR.get());
    try {
        TimeUnit.MINUTES.sleep(15);// 只是為了讓線程不終止
    } catch (InterruptedException ignored) { }
}, "thread-1");
t1.start();

Thread t2 = new Thread(() -> {
    System.out.println(Address.COMPANY.get());
    System.out.println(Address.YEAR.get());
    try {
        TimeUnit.MINUTES.sleep(15);// 只是為了讓線程不終止
    } catch (InterruptedException ignored) { }
}, "thread-2");
t2.start();
image.png
image.png
image.png
image.png

根據(jù)代碼和截圖內(nèi)容,我們總結(jié)下
線程2的哈希值1253254570的ThreadLocal存在索引10位置
線程2的哈希值-1401181199的ThreadLocal存在索引1位置
線程1的哈希值1253254570的ThreadLocal存在索引10位置
線程1的哈希值-1401181199的ThreadLocal存在索引1位置

JDK的ThreadLocal是根據(jù)它的哈希值然后再取模計(jì)算出索引位置,如果沖突還要再根據(jù)開(kāi)放地址法-線性探測(cè)繼續(xù)尋找下一個(gè)可用索引的位置.性能是比較低的. 那么我們看下在Netty中它是如何優(yōu)化這個(gè)功能的呢?

import io.netty.util.concurrent.FastThreadLocalThread;


FastThreadLocalThread t3 = new FastThreadLocalThread(() -> {

    System.out.println(Address.FAST_COMPANY.get());
    System.out.println(Address.FAST_YEAR.get());

    try {
        TimeUnit.MINUTES.sleep(15);
    } catch (InterruptedException ignored) {

    }

}, "thread-3");
t3.start();

FastThreadLocalThread t4 = new FastThreadLocalThread(() -> {

    System.out.println(Address.FAST_COMPANY.get());
    System.out.println(Address.FAST_YEAR.get());

    try {
        TimeUnit.MINUTES.sleep(15);
    } catch (InterruptedException ignored) {

    }

}, "thread-4");
t4.start();

這段代碼使用了Netty提供的FastThreadLocalThread線程,在它的內(nèi)部有個(gè)自己的InternalThreadLocalMap,這個(gè)Map是與FastThreadLocal搭配使用的.

FastThreadLocal內(nèi)部并不像JDK的ThreadLocal是根據(jù)哈希值與取模計(jì)算索引位置,它是在創(chuàng)建FastThreadLocal的時(shí)候就已經(jīng)確定了索引位置,在JVM中每個(gè)FastThreadLocal的索引值都是不同的.
根據(jù)索引直接存取值,時(shí)間復(fù)雜度O(1)

private final int index;
public FastThreadLocal() {
    // 創(chuàng)建FastThreadLocal時(shí)它的索引值index就確定下來(lái)了
    index = InternalThreadLocalMap.nextVariableIndex();
}
image.png

像Netty這樣追求性能的底層網(wǎng)絡(luò)框架,自己設(shè)計(jì)一個(gè)FastThreadLocalThread線程類,自己設(shè)計(jì)一個(gè)FastThreadLocal類,自己設(shè)計(jì)一個(gè)InternalThreadLocalMap類等.

公眾號(hào): Netty歷險(xiǎn)記

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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