線程局部變量ThreadLocal實現原理


  ThreadLocal,即線程局部變量,用來為每一個使用它的線程維護一個獨立的變量副本。這種變量只在線程的生命周期內有效。並且與鎖機制那種以時間換取空間的做法不同,ThreadLocal沒有任何鎖機制,它以空間換取時間的方式保證變量的線程安全。

  本篇從源碼方面分析ThreadLocal的實現原理。

  

  先看一下ThreadLocal類圖結構

  

  SuppliedThreadLocal主要是JDK1.8用來擴展對Lambda表達式的支持,有興趣的自行百度。

  ThreadLocalMap是ThreadLocal的靜態內部類,也是實際保存變量的類。

  Entry是ThreadLocalMap的靜態內部類。ThreadLocalMap持有一個Entry數組,以ThreadLocal為key,變量為value,封裝一個Entry。

  

  下面以一張圖簡要說明Thread,ThreadLocal,ThreadLocalMap和Entry的關系。

  

  說明一下上圖:

  1. 一個Thread擁有一個ThreadLocalMap對象
  2. ThreadLocalMap擁有一個Entry數組
  3. 每個Entry都有k--v
  4. Entry的key就是某個具體的ThreadLocal對象

  下面分析主要方法。

  1、set()

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

  這里可以看出:一個Thread只擁有一個ThreadLocalMap對象;具體存值調用的是ThreadLocalMap的set(),傳入的參數key就是當前ThreadLocal對象。

  再看看ThreadLocalMap的set()方法:

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1); // 1

            for (Entry e = tab[i];  // 2
                 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] = new Entry(key, value); // 3
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4
                rehash();
        }
  1. 通過key的hashCode與數組容量 -1 取模,計算數組index
  2. 從當前index開始遍歷,清除key為null的無效Entry
  3. 將K-V封裝為Entry,並放入數組
  4. 判斷是否需要進行Entry數組擴容。threshold的值為數組容量的2/3。

  看看擴容的resize()方法:

private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

  這里主要就是擴容為原先的2倍。然后遍歷舊數組,根據新數組容量重新計算Entry在新數組中的位置。

 

  2、get()

  ThreadLocal的get()方法如下:

public T get() {
        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();
    }

 

  ThreadLocalMap的getEntry()方法如下:

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1); // 1
            Entry e = table[i];
            if (e != null && e.get() == key) // 2
                return e;
            else
                return getEntryAfterMiss(key, i, e); //3
        }

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) { //4
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
  1. 計算index
  2. 當前index上的Entry不為空且key相同,直接返回
  3. 否則去相鄰index尋找
  4. 循環查找,發現無效key就清除。找到就結束循環。

3、remove()

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

  處理方式和查找保存類似,刪除對應Entry后都會去除key為null的無效元素。

 

注意

static class Entry extends WeakReference<ThreadLocal<?>> {}

  ThreadLocal可能存在OOM問題。因為ThreadLocalMap是使用ThreadLocal的弱引用作為key的,發生GC時,key被回收,這樣我們就無法訪問key為null的value元素,如果value本身是較大的對象,那么線程一直不結束的話,value就一直無法得到回收。特別是在我們使用線程池時,線程是復用的,不會殺死線程,這樣ThreadLocal弱引用被回收時,value不會被回收。

  在使用ThreadLocal時,線程邏輯代碼結束時,必須顯示調用ThreadLocal.remove()方法。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM