面試官:「我就是要問你 ThreadLocal 」

面試官:「先問一個問題,如何在多線程的環境下保證數據不被其餘線程修改?」java

能夠把這個數據用 ThreadLocal 封裝一下面試

面試官:「噢,那你說一下 ThreadLocal 是什麼吧,它和用鎖有什麼不同?」編程

ThreadLocal 提供了線程隔離的變量,它通常以 private static 的形式出現,能夠視做類中的全局變量,只不過每一個線程擁有本身的變量數據。 至於它和鎖有什麼不同,我以爲這個問題就不太好,由於它們根本就不是一類東西。對於數據使用方面來講,鎖主要解決的是併發環境下數據競爭的問題,而 ThreadLocal 根本就不是解決併發問題的,由於自己就不存在併發的業務。簡單來講,ThreadLocal 就比如身份證,咱們能夠拿身份證去網吧,去坐高鐵,去住酒店,但咱們每一個人都是用的本身的身份證,不存在什麼公共身份證你們搶着用。數據結構

面試官:「噢,看來你對使用場景很明白嘛,那你講講要怎麼保證線程隔離的呢?」多線程

這個簡單啊,誰的數據就直接給它本身保管就行了嘛,對應的在 Java 中就是把線程隔離的數據存在這個線程對應的 Thread 實例中。併發

面試官:「具體一點?」spa

JDK 的作法是實際上是每個 Thread 類中都保存了一個 ThreadLocalMap,以 ThreadLocal 爲 key,保存的數據爲 value, 這樣每一個線程就能有多個線程隔離的數據啦。如下面這段代碼爲例線程

public class Main {
    private static ThreadLocal<String> id = new ThreadLocal<>();
    private static ThreadLocal<String> phone = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread t1 = new Thread(()-> {
            id.set("2");
            phone.set("654321");
        });
        t1.start();

        id.set("1");
        phone.set("123456");
    }
}
複製代碼

對應的結構圖以下所示:code

所以,對 ThreadLocal 的讀寫,其實就是對 Thread 中的 ThreadLocalMap 的讀寫cdn

面試官:「能夠,你剛纔說到 Map 的讀寫了吧,那 ThreadLocalMap 和 HashMap 有什麼差異呢?」

須要注意的是 ThreadLocalMap 雖然也叫 Map,但並無繼承集合類中的 Map 接口,最大的差異再處理衝突上是不一樣的,集合類中的 Map 使用鏈地址法來處理衝突,而 ThreadLocalMap 使用的是開放尋址法,簡單的說就是若是衝突了,就找下一個索引的 Entry,直到找到空的爲止,對應的代碼爲:(PS:若是學過數據結構的話應該知道散列表的幾種衝突處理,就不用看下面代碼啦)

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
   // 使用hash來肯定索引
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
// 若是 tab[i] 已經被佔了,並且不是更新操做(即 k != key),那麼將索引+1,看看是否被佔了
// e = tab[i = nextIndex(i, len)]
// private static int nextIndex(int i, int len) {
// return ((i + 1 < len) ? i + 1 : 0);
// }
    }
    ...
}
複製代碼

面試官:"看來你還看過一些源碼嘛,那 ThreadLocalMap 中 Entry 的 key 對 ThreadLocal 是弱引用你總應該知道的吧?"

固然,由於 ThreadLocal 實例通常有兩個引用地方,一個是聲明 ThreadLocal 的地方,還一個是 ThreadLocalMap 中 key 的引用,這就致使了一個問題,若是除了 ThreadLocalMap 外再也不有引用的話,那麼 ThreadLocal 實例仍是沒法釋放,可是這個實例也不再會被訪問到了,也就是內存泄露。所以將其設爲弱引用的話,若是沒有其餘外部強引用後也能被回收。

面試官:「雖然 ThreadLocal 被回收了,可是 Entry 還在啊,裏面的 value 引用也是個內存泄露啊!」

天然是有相應對策的,在調用 ThreadLocalget()set() 等方法時可能會清除ThreadLocalMap 中 key 爲 nullEntry 對象。不過仍是建議在編程時使用 remove() 把不用了數據刪掉吧。另外就像開頭裏說的,建議是搭配 private static 使用的,畢竟卸載類仍是比較少見的吧,既然和線程同壽命,也就不存在泄露不泄露的問題了哈~

面試官:「嗯,那 ThreadLocal 就聊到這裏吧~」

相關文章
相關標籤/搜索