天天看點

Java并發之ThreadLocal源碼分析(第三篇:擷取元素)

前言

    get()方法用于擷取目前線程中以ThreadLocal對象為Key對象的線程局部變量

Java并發之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();  
    } 
           

用于擷取線程局部變量的方法

1、擷取目前線程對象

首先得到調用該get方法的Thread對象,并由臨時變量t負責存儲

2、擷取目前Thread對象持有的ThreadLocalMap對象

接着将Thread對象t傳入一個getMap()方法中,該方法會傳回一個Thread對象持有的ThreadLocalMap對象,接着由局部變量map負責臨時存儲ThreadLocalMap對象

3、檢查Therad對象是否持有一個ThreadLocalMap對象

若Thread對象持有的ThreadLocalMap對象未建立,則getMap()方法會傳回null,是以做出以下判斷

a、當map得到的是null時,說明目前Thread對象持有的ThreadLocalMap對象還未建立,則會調用一個setInitialValue()方法,setInitialValue()方法的傳回值最終成為get()方法的傳回值

b、當map擷取到目前Thread對象持有的ThreadLocalMap對象時,則先調用map的getEntry()方法擷取一個對象,getEntry()方法接受一個目前的ThreadLocal對象,該調用将會傳回一個ThreadLocalMap.Entry對象或者代表沒有比對元素的null,傳回的對象将由臨時變量e持有,是以這裡對兩種情況均做了處理

第一:e得到的是null,此時則會走到最下方的setInitialValue()方法中(見3号知識點),setInitialValue的傳回值将會作為get方法的最終傳回值(傳回的是線程局部變量對象)

第二:若e不是null,就取出ThreadLocal.Entry對象e持有的一個Object對象value,然後将value向下轉型為實際類型T,再指派給局部變量result進行存儲,最後則會傳回result儲存的對象,該result儲存的就是我們存儲的線程局部變量對象(也稱線程全局變量,在本線程内角度看的時候)

setInitialValue()方法分析

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
           

有兩種情況該set()方法會被調用

第一:擷取線程局部變量對象時,用于存儲key-value的ThreadLocalMap對象還未建立時,該方法會被調用

第二:擷取線程局部變量對象時,在目前Thread對象持有的ThreadLocalMap對象中,通過ThreadLocal對象作為key,沒有查找到比對的ThreadLocalMap.Entry對象,該方法會被調用

在該方法内部,首先會調用initialValue方法(見5号知識點),該方法用于傳回一個作為預設的線程局部變量對象,随後會将該值交由局部變量value進行持有,接着調用Thread的靜态方法currentThread(),獲得目前通路該方法的Thread對象,并由局部變量t持有,然後會把目前Thread對象t傳入到getMap方法中(見1号知識點),該方法傳回的ThreadLocalMap對象由局部變量map進行存儲,map的值有兩種情況

第一種情況:map指向的對象已經建立,此時map不為null,馬上調用它的set方法(),set方法(見6号知識點)是哈希表插入元素的方法,接受兩個參數,一個就是目前的ThreadLocal對象,另一個就是通過initialValue方法(見5号知識點)得到的預設局部局部變量對象

第二種情況:map為null,此時則會調用一個createMap方法(見7号知識點),同樣也是把目前ThreadLocal對象作為key,預設的Value對象作為value

最後該方法會傳回線程局部變量對象value,而傳回值value對象則會成為get方法(見0号知識點)的傳回值

getEntry()方法分析(此方法位于ThreadLocalMap類中)

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);
        }
           

傳入的ThreadLocal對象作為key,首先獲得key的hashCode值,該值正是由ThreadLocal對象持有的threadLocalHashCode執行個體變量儲存着,接着将獲得key的hashCode值與(底層數組長度-1)進行一個按位與運算,計算出的值正是桶的位址(哈希位址),該值交由臨時變量i進行存儲,将桶的下标i傳入到底層數組對象table中得到的Entry對象,由變量e進行保管。

熟悉的代碼告訴我,ThreadLocalMap是哈希表結構,它的底層數組容量也一定是2的n次方,隻有這樣按位與運算才等同于取模運算!(詳情見HashMap)

set()方法分析(注意:這是ThreadLocalMap的set()方法,不是TheradLocal的set()方法)

private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            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)]) {
                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);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
           

TheradLocalMap的set()方法用于添加元素,此處省略了……将在ThreadLocalMap中分析

總結

1、Thread對象持有的ThreadLocalMap對象,它是一個哈希表結構的對象,在ThreadLocalMap持有的數組對象中,存儲着以ThreadLocal對象為Key對象,線程局部變量對象為Value對象的ThreadLocal.Entry對象

2、每個Thread對象持有的ThreadLocalMap對象threadLocals,是在ThreadLocal類中的createMap()方法中建立的