ThreadLocal是如何實現保存線程私有對象的

Looper中的ThreadLocal

最早知道ThreadLocal是在Looper的源碼裏,用一個ThreadLocal保存了當前的looper對象。

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

    //所有Looper都需要調用的prepare方法,實例化新的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保存的是線程內獨立的對象,所以在哪個線程調用,取到的就是哪個線程的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();
    }        

當時就查了下ThreadLocal,發現是保存線程私有對象的容器,以爲就是一個類似hashmap,用線程做key,存value。最近看了下並不是如此,實際上是以ThreadLocal自身爲key來存儲對象的。於是來學習下ThreadLocal源碼是如何做到保存線程私有對象的。

ThreadLocal和ThreadLocalMap以及Thread的關係

ThreadLocal、ThreadLocalMap、Thread之間的關係和我們下意識中想的不太一樣,不過一步步看下去之後就能明白爲啥ThreadLocal能保存線程私有對象了。

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

這個是Thread源碼,每一個Thread都有一個ThreadLocalMap對象,而ThreadLocalMap是ThreadLocal的內部類,是實際存儲對象的容器。

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

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

        //線程初始化後,會實例化一個ThreadLocalMap,map裏有table,table裏存Entry。
        //因此,一個線程對應一個ThreadLocalMap,一個ThreadLocalMap對應多個Entry。
        //每個Entry對應一個ThreadLocal作爲key,所以一個ThreadLocal只能存一個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();

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

                //有空的位置,存入
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //到這裏還沒return,說明table存滿了,需要擴容
            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);
        }
    } 

基本關係知道了,最後再來看看ThreadLocal的方法:

    //ThreadLocal的get方法
    public T get() {
        //獲取當前線程
        Thread t = Thread.currentThread();
        //拿到當前線程的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //因爲ThreadLocalMap存的時候就是拿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();
    }

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

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

那麼現在基本完全清楚ThreadLocal與Thread還有ThreadLocalMap之間的關係了。
每個Thread都有一個成員變量ThreadLocalMap。這個ThreadLocalMap對象不是public,所以外部調用不了,可以看做是線程私有對象。
ThreadLocalMap存了一個table,table裏保存了一些entry,每個entry對應一個key和value,而這個key就是ThreadLocal對象。
因此一個ThreadLocal只能存一個value,但是可以通過new多個ThreadLocal來保存多個線程私有對象。

ThreadLocal內存泄露

在上面的源碼中我們看到Entry裏持有的ThreadLocal對象是弱引用持有,因此ThreadLocal不會因爲線程持有而泄露,比如我們Android的主線程,正常使用過程中是不會掛掉的。
但是Enrty的value的是強引用的,因此ThreadLocal中的value還是會因爲線程持有而無法回收。如果繼續看源碼的話,會發現在ThreadLocalMap的resize和expungeStaleEntry方法裏會檢查key爲空的value值爲空幫助GC。
因此爲了避免value內存泄露,我們需要在ThreadLocal不需要的時候主動remove掉。

併發修改問題

    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。因此這個threadLocalHashCode就十分重要了。
這裏需要保證threadLocalHashCode是唯一的,否則兩個線程同時創建ThreadLocal得到相同的hashcode,那就破壞了ThreadLocalMap的線程私有特性了。
這裏生成threadLocalHashCode是通過一個靜態對象nextHashCode不斷增加來獲得的。那怎麼保證多個進程併發實例化ThreadLocal對象,不會生成相同的hashcode呢?
答案是AtomicInteger,通過這個類來保證變量自增操作在多線程操作時候的原子性。
我們知道Synchronized也可以保證原子性,但相比於它,AtomicInteger類是通過非阻塞方法來實現原子性的,需要的性能開銷更小。
這種非阻塞實現原子性的方法和處理器的CAS指令有關,感興趣的小夥伴自行研究吧~

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