Java併發之ThreadLocal(源碼淺析第一篇:直接從get()方法開始)

0、ThreadLocal,線程局部變量,或叫線程本地變量均可

1、一般共享變量都是所有線程共享的,而線程局部變量,名字也很貼切,就是它作用域只在線程內被訪問,算是解決共享變量併發問題的一種思路,不修改原來的共享變量,而是自己複製了一份,自己用

 

2、目前已經知道解決併發問題的三種思路

a、CAS方式

b、鎖方式

c、線程局部變量方式

 

3、ThreadLocal裏面巧妙的利用了Thread下的一個哈希表,裏面有Entry,其中key就是當前的ThreadLocal,value就是保存的線程局部變量對象,更巧妙的是這個哈希表是在ThreadLocal下創建的,艹,牛逼

 

4、從new一個ThreadLocal開始

    public ThreadLocal() {
    }

構造方法很清爽,就是new了一個對象而已

 

5、我們再去調用ThreadLocal的get()方法,ThreadLocal是泛型類,所以你new的時候,要指定一個類型

    public T get() {
        Thread t = Thread.currentThread(); //當前線程對象
        ThreadLocalMap map = getMap(t);    //往下看第6個步驟
        if (map != null) {             
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();    //往下看第8個步驟
    } 

a、如果沒有通過ThreadLocal設置過初始對象,則get()方法返回initialValue()方法返回的對象

b、所以你經常看到繼承了ThreadLocal後,重寫了initialValue方法

c、如果你通過ThreadLocal設置過初始對象,則get()方法返回你設置的對象

 

6、getMap()方法我們是訪問不了的,default權限

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals; //請看7號步驟
    }

我們看到它返回一個thread對象下的實例變量,叫做threadLocals,接下來我們看看threadLocals的類型

 

7、位於Thread類下

ThreadLocal.ThreadLocalMap threadLocals = null;

它的類型是ThreadLocalMap,ThreadLocalMap是ThreadLocal下的一個靜態內部類

threadLocals在Thread下只在一個叫做exit()方法下做了賦值操作(不算初始化的null)

    private void exit() {
        //省略了很多代碼……

        threadLocals = null; //看我
        
        //省略了很多代碼……
    }

 

8、那麼threadLocals在哪裏賦值的呢???彆着急,我們繼續看代碼,因爲上面的map爲null,所以走到了這裏

    private T setInitialValue() {
        T value = initialValue(); //請看9號步驟
        Thread t = Thread.currentThread(); //當前線程對象
        ThreadLocalMap map = getMap(t); //又一次調用getMap()方法,請看6號步驟
        if (map != null)
            map.set(this, value);  //不爲null,爲哈希表設置元素
        else
            createMap(t, value); //將當前thread、存儲的第一個對象,傳進去,請看10號步驟
        return value;
    }

 

9、initialValue方法也是繼承ThreadLocal時經常要重寫的一個方法,返回一個ThrealLocal存儲的第一個對象

    protected T initialValue() {
        return null;
    }

它的默認實現是返回null

 

10、開心不開心,我們終於找到了爲t.threadLocals賦值對象的地方,牛逼

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue); //new了一個ThreadLocalMap對象,把當前ThreadLocal對象與第一個要存儲的對象傳了進去,我們去看看ThreadLocalMap的構造方法
    }

 

11、沒錯,一個自定義的哈希表出來了,這邊將key與value傳進來,要構造哈希表的第一個Entry了

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY]; //初始16個容量的數組,底層數組,元素限定爲了Entry,Entry是ThreadLocalMap下的靜態內部類,牛逼
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //計算哈希地址,請看第12號
            table[i] = new Entry(firstKey, firstValue); //安心將Entry放入對應的哈希地址中,key和value隨着Entry存儲起來了
            size = 1; //哈希表元素總數加1
            setThreshold(INITIAL_CAPACITY); //設置閾值,把默認長度傳進去,請看13號
        }

 

12、先去ThreadLocal下去看下threadLocalHashCode的值

private final int threadLocalHashCode = nextHashCode();
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    private static AtomicInteger nextHashCode =
        new AtomicInteger();  //用了線程安全的原子類,AtomicInteger,默認值是0
private static final int HASH_INCREMENT = 0x61c88647; //增量值用的牛逼

增量值與16-1做了一個位與運算,這樣拿到哈希地址,歐耶

 

13、看下閾值是:16 * 2 / 3, 即 32 / 3 = 10,閾值是10

        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

 

14、借了一張圖,還不錯

ThreadLocal1代表一個對象

ThreadLocal2也代表一個對象

一個ThreadLocal對象只能存放一個線程局部變量,你可以創建多個ThreadLocal,那樣就可以爲一個線程保存多個線程局部變量了

 

protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的,哈哈,艹

 

參考文章:https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484118&idx=1&sn=da3e4c4cfd0642687c5d7bcef543fe5b&chksm=ebd743d7dca0cac19a82c7b29b5b22c4b902e9e53bd785d066b625b4272af2a6598a0cc0f38e#rd

https://www.jianshu.com/p/98b68c97df9b 不錯的文章

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