ThreadLocal的簡單理解

[TOC]

一、背景

最近有人問我ThreadLocal是如何做到在每個(gè)線程中的值都是隔離的,此處寫篇文章來簡單記錄下。

二、ThreadLocal解決的問題

  1. 該數(shù)據(jù)屬于該線程Thread自身,別的線程無法對其影響。(需要注意:需要調(diào)用ThreadLocal的remove方法)
  2. 不存在線程安全問題。(因?yàn)?code>ThreadLocal類型的變量只有自身的線程可以訪問,所以這點(diǎn)是成立的。)

比如:

用戶登錄成功后,需要將登錄用戶信息保存起來,以方便在系統(tǒng)中的任何地方都可以使用到,那么此時(shí)就可以使用ThreadLocal來實(shí)現(xiàn)。例如:Spring Security中的ThreadLocalSecurityContextHolderStrategy類。

三、如何創(chuàng)建一個(gè)ThreadLocal實(shí)例

private static final ThreadLocal<String> USER_NAME = new ThreadLocal<>();

ThreadLocal的實(shí)例推薦使用private static final來修飾。

四、ThreadLocal如何做到線程變量隔離

1、理解3個(gè)類

  1. ThreadLocal: 此類提供了一個(gè)簡單的set,get,remove方法,用于設(shè)置,獲取或移除 綁定到線程本地變量中的值。

  2. ThreadLocalMap: 這是在ThreadLocal中定義的一個(gè)類,可以簡單的將它理解成一個(gè)Map,不過它的key是WeakReference弱引用類型,這樣當(dāng)這個(gè)值沒有在別的地方引用時(shí),在發(fā)生垃圾回收時(shí),這個(gè)map的key會被自動(dòng)回收,不過它的值不會被自動(dòng)回收。

    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            // key 弱引用
            super(k);
            // 值強(qiáng)引用
            value = v;
        }
    }
    
  3. Thread:這個(gè)是線程類,在這個(gè)類中存在一個(gè)threadLocals變量,具體的類型是ThreadLocal.ThreadLocalMap。

2、看下set方法是如何實(shí)現(xiàn)的

public void set(T value) {
    // 獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    // 獲取綁定到這個(gè)線程自身的 ThreadLocalMap,這個(gè)ThreadLocalMap是從Thread類的`threadLocals`變量中獲取的
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 向map中設(shè)置值,key為 ThreadLocal 對象的實(shí)例。
        map.set(this, value);
    } else {
        // 如果map不存在,則創(chuàng)建出來。
        createMap(t, value);
    }
}

通過上方的代碼,我們可知: 當(dāng)我們向ThreadLocal中設(shè)置一個(gè)值,會經(jīng)過如下幾個(gè)步驟:

  1. 獲取當(dāng)前線程Thread
  2. 獲取當(dāng)前線程的 ThreadLocalMap 對象。
  3. ThreadLocalMap中設(shè)置值,key為ThreadLocal對象,值為具體的值。

3、看看 get 方法如何實(shí)現(xiàn)

public T get() {
    // 獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    // 獲取這個(gè)線程自身綁定的 ThreadLocalMap 對象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // this是ThreadLocal對象,獲取Map中的Entry對象
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            // 獲取具體的值
            T result = (T)e.value;
            return result;
        }
    }
    // 設(shè)置初始值
    return setInitialValue();
}

從上方的get 和 set 方法可以得知,通過往ThreadLocal對象中設(shè)置值或獲取值,其實(shí)是最終操作到Thread對象中的threadLocals字段中,而這個(gè)字段是Thread自身的,因此做到了隔離。

五、ThreadLocalMap中的hash沖突是如何處理的

1、ThreadLocal對象的hash值是怎樣的

private final int threadLocalHashCode = nextHashCode();
    // 該 ThreadLocal 對象自身的hash code值
    private final int threadLocalHashCode = nextHashCode();
    // 從0開始
    private static AtomicInteger nextHashCode = new AtomicInteger();
    // 每次遞增固定的值
    private static final int HASH_INCREMENT = 0x61c88647;
    // hash code 值計(jì)算
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

從上方的代碼中可以,ThreadLocal類在實(shí)例化出來之后,它的hash code值(threadLocalHashCode)就是固定的,即使ThreadLocal調(diào)用了set方法,設(shè)置了別的值,它的hash code值也不會發(fā)生變化。

此字段threadLocalHashCodeThreadLocal對象的hash值,在ThreadLocalMap中需要用到這個(gè)hash值。

2、解決hash沖突

ThreadLocalMap hash沖突.png

ThreadLocalMap解決hash沖突的辦法很簡單。就是通過線性探測法。如果發(fā)生了沖突,就去找數(shù)組后面的可用位置。具體看上圖。演示的是A和B 2個(gè)ThreadLocal對象,然后發(fā)生了沖突,A和B存在的位置在那個(gè)地方。

六、ThreadLocal內(nèi)存泄漏

ThreadLocal為什么會存在內(nèi)存泄漏呢?

這是因?yàn)?code>ThreadLocalMap中的keyWeakReference類型,也就是弱引用類型,而弱引用類型的數(shù)據(jù)在沒有外部強(qiáng)引用類型的話,在發(fā)生gc的時(shí)候,會自動(dòng)被回收掉。注意: 此時(shí)是key被回收了,但是value是沒有回收的。因此在ThreadLocalMap中的Entry[]中可能存在keynull,但是value是具體的值的對象,因此就發(fā)生了內(nèi)存泄漏。

解決內(nèi)存泄漏:

當(dāng)我們使用完ThreadLocal對象后,需要在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用ThreadLocal#remove()方法。 否則就只有等Thread自動(dòng)退出才能清除,如果是使用了線程池,Thread會重用,清除的機(jī)會就更難。

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

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

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