從ThreadLocal的作用進行分析,你會更加了解它

菠蘿印象威發表於2020-11-10

本篇文章主要講解Java併發中的一個重要知識點—ThreadLocal

ThreadLocal簡介

ThreadLocal是一個本地執行緒副本變數類,它也是執行緒內部類的儲存類,可以在指定執行緒內部儲存資料,且只有指定的執行緒可以獲取儲存的資料。

ThreadLocal提供了執行緒記憶體儲變數的能力,這些變數在每一個執行緒中都是相互獨立的,通過set和get方法可以得到當前執行緒對應的值。

ThreadLoacl的相關方法

1.set()方法

public void set(T value) {
   //獲取當前執行緒
   Thread t = Thread.currentThread();
   //儲存的資料結構型別
   ThreadLocalMap map = getMap(t);
   //如果存在map就直接set,沒有則建立map並set
   if (map != null)
	map.set(this, value);
   else
	createMap(t, value);
}

從set()方法可以看到:
1).每次set時都先獲取當前的執行緒,在當前執行緒的基礎上進行操作。
2).獲取該執行緒物件的ThreadLocalMap
3).如果ThreadLocalMap存在,就直接set,沒有就先建立再set

看下ThreadLocalMap

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;
}

看完ThreadLocalMap後發現:ThreadLocalMap是ThreadLocal的一個靜態內部類,其中又建立了靜態類Entry用來儲存變數,且Entry中的key是ThreadLocal的物件,value在ThreadLocal確定的下標位置。

再看一下getMap()方法和createMap()方法

//ThreadLocal類中獲取Thread類中的ThreadLocalMap物件
ThreadLocalMap getMap(Thread t) {	
	return t.threadLocals;    
}
//建立map
void createMap(Thread t, T firstValue) {
     t.threadLocals = new ThreadLocalMap(this, firstValue);
}

從getMap()可以直接獲取當前執行緒的區域性變數threadLocals(ThreadLocalMap型別)。

從createMap()方法可知:建立map時,例項化一個新的ThreadLocalMap,並賦值給當前執行緒的成員變數threadLocals。

看下threadLocals(ThreadLocalMap型別)區域性變數的建立

ThreadLocal.ThreadLocalMap threadLocals = null;

threadLocals是ThreadLocal中的靜態內部類ThreadLocalMap型別的一個物件,初始化為null。

再看下ThreadLocalMap的set方法就大功告成啦!

//ThreadLocalMap的set方法
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();

        //k 相等則覆蓋舊值
        if (k == key) {
            e.value = value;
            return;
        }

        //此時說明此處 Entry 的 k 中的物件例項已經被回收了,需要替換掉這個位置的 key 和 value
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    //建立 Entry 物件
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
    
    //獲取 Entry
    private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e); 
   }
}

其實,通過原始碼可知,ThreadLocalMap的一些方法和HashMap的方法很類似,都是先獲取key的hash值,確定key在table中的索引位置,然後再判斷set需要修改的位置和當前獲取的位置是否一致,如果一致說明找到陣列位置了則覆蓋對應的值。

總結一下主要步驟如下:
1).建立陣列
2).獲取key的hash值,作為陣列的下標
3).如果陣列當前位置有物件,獲取此位置
4).判斷key和當前陣列位置是不是同一個位置,如果是同一個位置,則說明找到此位置的值,進行覆蓋。

看到這裡大家可能很混亂,為了研究ThreadLocal的set()方法,竟然帶出了一系列的方法,那麼,如何將它們整合到一起呢?

我們還是回到最初的ThreadLocal的set()方法,一步步解讀:

public void set(T value) {
   //獲取當前執行緒
   Thread t = Thread.currentThread();
   //儲存的資料結構型別
   ThreadLocalMap map = getMap(t);
   //如果存在map就直接set,沒有則建立map並set
   if (map != null)
       map.set(this, value);
   else
       createMap(t, value);
}

解讀步驟:
1.呼叫ThreadLocal的set()方法時,會先獲取當前執行緒。
2.獲取ThreadLocal的靜態內部類,通過getMap()方法獲取,getMap()方法返回的是當前執行緒的區域性變數threadLocals(ThreadLocalMap型別)。
3.如果存在ThreadLocalMap的物件,就直接set
4.如果沒有建立ThreadLocalMap的物件,則使用當前執行緒建立,使用creatMap()方法,例項化一個新的ThreadLocalMap,並賦值給當前執行緒的成員變數threadLocals。

總結:
1.ThreadLocal在每個執行緒中都建立了一個ThreadLocalMap物件,且所有的操作都在這個物件裡。
2.ThreadLocalMap是ThreadLocal的靜態內部類,用Entry來進行儲存。
3.呼叫ThreadLocal的set()方法時就是對ThreadLocalMap進行設定值,key是ThreadLocal物件。

ThreadLocal、ThreadLocalMap和Thread的關係

某個執行緒執行->執行緒為每個ThreadLocal建立了一個ThreadLocalMap物件->ThreadLocalMap中用Entry來進行儲存,key為ThreadLocal的引用,value是對應的值。

2.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();
    }

看完set()方法,發現get()方法簡單了許多,其中大體和set()方法類似,從Entry中獲取變數是使用map.getEntry()方法。

總結:我們使用ThreadLocal類是為了使得每個執行緒在處理各自的變數時互不干擾,而ThreadLocal的互不干擾就體現在:每次呼叫其方法時都先獲取當前的執行緒,返回當前執行緒的ThreadLocalMap,實際上ThreadLocal並不會儲存值,真正儲存值得是它的靜態內部類ThreadLocalMap,而ThreadLocal呼叫set()和get()方法時,也會傳遞給ThreadLocalMap進行對應的操作,真正實現了一個執行緒操作執行緒自己的區域性變數。

相關文章