ThreadLocal是如何實(shí)現(xiàn)保存線程私有對(duì)象的

Looper中的ThreadLocal

最早知道ThreadLocal是在Looper的源碼里,用一個(gè)ThreadLocal保存了當(dāng)前的looper對(duì)象。

    //一個(gè)靜態(tài)ThreadLocal對(duì)象,因此一個(gè)進(jìn)程里的所有Looper都共用這個(gè)ThreadLocal
    // sThreadLocal.get() will return null unless you've called prepare().
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    //所有Looper都需要調(diào)用的prepare方法,實(shí)例化新的Looper并保存到ThreadLocal
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    //由于ThreadLocal保存的是線程內(nèi)獨(dú)立的對(duì)象,所以在哪個(gè)線程調(diào)用,取到的就是哪個(gè)線程的Looper
    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }        

當(dāng)時(shí)就查了下ThreadLocal,發(fā)現(xiàn)是保存線程私有對(duì)象的容器,以為就是一個(gè)類似hashmap,用線程做key,存value。最近看了下并不是如此,實(shí)際上是以ThreadLocal自身為key來存儲(chǔ)對(duì)象的。于是來學(xué)習(xí)下ThreadLocal源碼是如何做到保存線程私有對(duì)象的。

ThreadLocal和ThreadLocalMap以及Thread的關(guān)系

ThreadLocal、ThreadLocalMap、Thread之間的關(guān)系和我們下意識(shí)中想的不太一樣,不過一步步看下去之后就能明白為啥ThreadLocal能保存線程私有對(duì)象了。

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

這個(gè)是Thread源碼,每一個(gè)Thread都有一個(gè)ThreadLocalMap對(duì)象,而ThreadLocalMap是ThreadLocal的內(nèi)部類,是實(shí)際存儲(chǔ)對(duì)象的容器。

    static class ThreadLocalMap {
        //可以看到這里持有的ThreadLocal對(duì)象是弱引用,
        //防止線程不死(比如android主線程)ThreadLocal無法被回收。
        //這里可以看到Entry的key是ThreadLocal,value是要保存的對(duì)象。
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

        //線程初始化后,會(huì)實(shí)例化一個(gè)ThreadLocalMap,map里有table,table里存Entry。
        //因此,一個(gè)線程對(duì)應(yīng)一個(gè)ThreadLocalMap,一個(gè)ThreadLocalMap對(duì)應(yīng)多個(gè)Entry。
        //每個(gè)Entry對(duì)應(yīng)一個(gè)ThreadLocal作為key,所以一個(gè)ThreadLocal只能存一個(gè)value。
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        //ThreadLocalMap保存方法
        private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            //通過ThreadLocal的hashcode獲得索引i
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                //驗(yàn)證k相等,replace value的值
                if (k == key) {
                    e.value = value;
                    return;
                }

                //有空的位置,存入
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //到這里還沒return,說明table存滿了,需要擴(kuò)容
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        //ThreadLocalMap獲取方法,還是通過hashcode獲取table中的索引
        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);
        }
    } 

基本關(guān)系知道了,最后再來看看ThreadLocal的方法:

    //ThreadLocal的get方法
    public T get() {
        //獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //拿到當(dāng)前線程的ThreadLocalMap對(duì)象
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //因?yàn)門hreadLocalMap存的時(shí)候就是拿ThreadLocal作key
            //因此這里傳入this獲取entry,再拿到value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    //獲取當(dāng)前線程的ThreadLocalMap對(duì)象
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    //ThreadLocal的set方法
    public void set(T value) {
        //獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //拿到當(dāng)前線程的ThreadLocalMap對(duì)象
        ThreadLocalMap map = getMap(t);
        //this作key傳入存儲(chǔ)value
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

那么現(xiàn)在基本完全清楚ThreadLocal與Thread還有ThreadLocalMap之間的關(guān)系了。
每個(gè)Thread都有一個(gè)成員變量ThreadLocalMap。這個(gè)ThreadLocalMap對(duì)象不是public,所以外部調(diào)用不了,可以看做是線程私有對(duì)象。
ThreadLocalMap存了一個(gè)table,table里保存了一些entry,每個(gè)entry對(duì)應(yīng)一個(gè)key和value,而這個(gè)key就是ThreadLocal對(duì)象。
因此一個(gè)ThreadLocal只能存一個(gè)value,但是可以通過new多個(gè)ThreadLocal來保存多個(gè)線程私有對(duì)象。

ThreadLocal內(nèi)存泄露

在上面的源碼中我們看到Entry里持有的ThreadLocal對(duì)象是弱引用持有,因此ThreadLocal不會(huì)因?yàn)榫€程持有而泄露,比如我們Android的主線程,正常使用過程中是不會(huì)掛掉的。
但是Enrty的value的是強(qiáng)引用的,因此ThreadLocal中的value還是會(huì)因?yàn)榫€程持有而無法回收。如果繼續(xù)看源碼的話,會(huì)發(fā)現(xiàn)在ThreadLocalMap的resize和expungeStaleEntry方法里會(huì)檢查key為空的value值為空幫助GC。
因此為了避免value內(nèi)存泄露,我們需要在ThreadLocal不需要的時(shí)候主動(dòng)remove掉。

并發(fā)修改問題

    private final int threadLocalHashCode = nextHashCode();

    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

ThreadLocal通過自身的threadLocalHashCode來碰撞得到自己在ThreadLocalMap的table里的索引i。因此這個(gè)threadLocalHashCode就十分重要了。
這里需要保證threadLocalHashCode是唯一的,否則兩個(gè)線程同時(shí)創(chuàng)建ThreadLocal得到相同的hashcode,那就破壞了ThreadLocalMap的線程私有特性了。
這里生成threadLocalHashCode是通過一個(gè)靜態(tài)對(duì)象nextHashCode不斷增加來獲得的。那怎么保證多個(gè)進(jìn)程并發(fā)實(shí)例化ThreadLocal對(duì)象,不會(huì)生成相同的hashcode呢?
答案是AtomicInteger,通過這個(gè)類來保證變量自增操作在多線程操作時(shí)候的原子性。
我們知道Synchronized也可以保證原子性,但相比于它,AtomicInteger類是通過非阻塞方法來實(shí)現(xiàn)原子性的,需要的性能開銷更小。
這種非阻塞實(shí)現(xiàn)原子性的方法和處理器的CAS指令有關(guān),感興趣的小伙伴自行研究吧~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容