Java併發之ThreadLocal源碼分析(第二篇:獲取)

get方法獲取在當前線程中以ThreadLocal對象爲key的線程局部變量對象

 

0、無參

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

首先得到調用該get方法的Thread對象,並由臨時變量t負責存儲,接着將Thread對象t傳入一個getMap方法中(見1號知識點),該方法會返回一個Thread對象持有的ThreadLocalMap對象,接着由局部變量map負責臨時存儲ThreadLocalMap對象,若Thread對象持有的ThreadLocalMap對象未創建,則getMap方法是會返回null的,所以做了以下判斷

a、當map得到的是null時,說明當前Thread對象持有的ThreadLocalMap對象還未創建,則會調用一個setInitialValue方法(見3號知識點),setInitialValue方法的返回值會最終作爲get方法的返回值

b、當map成功獲取當前Thread對象持有的ThreadLocalMap對象時,則會先調用map的getEntry方法(見4號知識點)獲取一個對象,getEntry方法接受一個當前的ThreadLocal對象,該調用將會返回一個ThreadLocalMap.Entry對象或者代表沒有匹配元素的null,返回的對象將由臨時變量e持有,所以這裏對兩種情況均做了處理

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

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

 

1、一個參數,接受一個Thread對象

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

返回Thread對象t持有的一個ThreadLocalMap對象(見2號知識點)

 

2、字段(注意:該字段位於Thread類中)

    ThreadLocal.ThreadLocalMap threadLocals = null;

每個Thread對象持有了一個實例變量threadLocals,該變量的類型是ThreadLocal.ThreadLocalMap,其中ThreadLocalMap爲ThreadLocal下的靜態內部類

 

3、無參

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

有兩種情況該方法會被調用(兩個邏輯均位於get方法中(見0號知識點))

第一:獲取線程局部變量對象時,用於存儲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號知識點)的返回值

 

 

4、一個參數,接受一個ThreadLocal對象(該方法位於ThreadLocalMap類中,ThreadLocalMap類爲ThreadLocal的靜態內部類)

        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)

 

5、無參

    protected T initialValue() {
        return null;
    }

該方法的返回值會作爲一個初始存儲的線程局部變量對象,此處用protcted修飾,明擺着是要你去子類中重寫該方法,這樣當該方法被調用時,就能返回一個你定義好的線程局部變量對象了,當然也可以不重寫,默認的線程局部變量對象是null就是了…………

 

6、兩個參數,接受一個ThreadLocal對象,一個Object對象(該方法位於ThreadLocalMap中)

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

向哈希表中插入元素的方法(單獨分析)

 

7、兩個參數,接受一個Thread對象,接受一個線程局部變量對象T

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

首先爲傳入的Thread對象t中持有的threadLocals實例變量進行初始化工作,此處會new一個ThreadLocalMap對象,調用ThreadLocalMap的構造方法時,爲其傳入當前的ThreadLocal對象,再將傳入進來的T類型的線程局部變量對象firstValue也傳進去,創建好的ThreadLocalMap對象將由Thread對象的實例變量threadLocals持有。說明該方法的就是負責幫當前Thread對象持有的ThreadLocalMap對象進行的初始化工作

 

總結:

a、Thread對象持有一個ThreadLocalMap對象,它作爲哈希表對象,負責在內存中持有以ThreadLocal對象爲key,線程局部變量對象爲value的ThreadLocal.Entry對象

b、每個Thread對象持有的ThreadLocalMap對象,是在ThreadLocal類中進行的初始化,具體位置就是在ThreadLocal中的createMap方法中

c、你可以通過繼承ThreadLocal類,重寫initialValue方法,這樣就可以設置一個默認存儲的線程局部變量對象,即創建完ThreadLocal對象後,直接通過get方法獲取的對象一定是initialValue方法的返回值

d、在ThreadLocal內部,實現了一個ThreadLocalMap的靜態內部類,該類是容器類,結構是哈希表

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章