ThreadLocal 机制解析

ThreadLocal

在Android开发中,Handler消息处理机制中用到了ThreadLocal类,花了点时间对它进行解析。

Thread类中有个成员变量threadLocals,类型为ThreadLocal.ThreadLocalMap,是ThreadLocal自定义的一个hashmap,它的key是ThreadLocal<?>类型,value是Object。如下:

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

注意:在Thread中threadLocals参数并没有被赋值,所以默认为null

ThreadLocal类中,有这样一个函数

    //初始化值
    protected T initialValue() {
        return null;
    }

这个函数用来初始化在一个线程中的初始值,默认返回null,建议使用ThreadLocal时重写。

再看看这个函数

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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 the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

从setInitialValue()方法名可以看出是设置ThreadLocal的初始值,先是获取当前线程 t ,然后通过getMap(t)方法获取当前线程 t 的threadLocals变量,就是一个ThreadLocalMap实例,通过上面的getMap(Thread t)方法看出返回的map应该是null,所以执行createMap(t,value)方法。

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

这个方法很关键,通过createMap()方法使Thread的成员变量threadLocals赋值了,并把this当做key,把通过initialValue()方法返回的值作为value(所以重写initialValue()的话就避免了初始化值为null的尴尬)。

同时我们发现在ThreadLocal类中的set()方法也调用了createMap()方法,是不是有种顿时豁然开朗的感觉。

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

我们先看看哪里调用了setInitialValue()法

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

发现整个ThreadLocal类中只有这一个地方调用了这个方法。看了这里就应该明白了吧。

  • 在没有调用set()方法的情况下, 如果第一次调用get()方法,getMap()肯定返回一个null的ThreadLocalMap对象,就会执行 return setInitialValue(); 语句,返回初始化的值。
  • 如果调用了set()方法的情况下,可以看出set方法也调用了createMap(t, value);来给Thread的成员变量threadLocals赋值,那么getMap()肯定就返回一个不为null的ThreadLocalMap对象,把this作为对象来获取value。

小结

  • Thead中有一个类型为ThreadLocalMap的成员变量threadLocals,并且初始值为null
  • ThreadLocalMap是一个ThreadLocal自定义的HashMap,键为ThreadLocal<?>,值为Object
  • ThreadLocal默认会有一个初始值null,你可以通过重写initialValue()方法或者调用set()方法来改变这个初始值,他们的本质都是去调用createMap()方法给当前的线程Thread的成员变量threadLocals赋值。
  • ThreadLocal的set()方法通过获取当前线程 t ,通过 t 的成员变量threadLocals的set()方法来去保存value值,并把当前对象this作为key。
  • 调用ThreadLocal的get()方法来获取value,实质就是获取当前线程t的成员变量threadLocals,并且把自身作为key来获取value的过程。

每个Thread都维护了一个ThreadLocalMap对象,也就是threadLocals变量,通过它就可以保存各种不同类型的ThreadLocal和对应的值

Demo示例:

public class ThreadStudy {

    private static OneThread oneThread;
    private static TwoThread twoThread;
    private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 99;
        }
    };
    private static ThreadLocal<String> stringThreadLocal = new InheritableThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "Hello world";
        }
    };

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        oneThread = new ThreadStudy().new OneThread();
        twoThread = new ThreadStudy().new TwoThread();
        oneThread.start();
        twoThread.start();

    }

    class OneThread extends Thread{

        public OneThread(){
        }

        @Override
        public void run() {
            //给Thread的threadLocals变量赋值,并把stringThreadLocal作为key,"设置value值"作为value保存在threadLocals里。
            stringThreadLocal.set("设置value值");
            System.out.println(Thread.currentThread().getName() + "  " + stringThreadLocal.get());
        }
    }

    class TwoThread extends Thread{

        public TwoThread(){
        }
        @Override
        public void run() {
            //由于没有调用set方法,所以会在第一次调用get()方法中去个给Thread的threadLocals变量赋值
            System.out.println(Thread.currentThread().getName() + "  " + integerThreadLocal.get());
            System.out.println(Thread.currentThread().getName() + "  " + stringThreadLocal.get());
        }
    }
}

输出结果:

Thread-0 设置value值
Thread-1 99
Thread-1 Hello world

相信看到这里对ThreadLocal都理解了吧。

话说ThreadLocalMap类到底怎么工作的呢,下面我们一起来看看。

ThreadLocalMap

在ThreadLocalMap中有个静态内部类Entry

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

Entry继承了WeakReference(弱引用)类,应该这里准确的说是ThreadLocal作为Entry的key并成了弱引用。

其实在HashMap中也有这样的一个同名的接口类,关系是:HashMap<K,V> 继承了AbstractMap<K,V> ,然后AbstractMap<K,V> 实现了 Map<K,V>,在Map<K,V>接口中就有了Entry<K,V>接口。

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

ThreadLocalMap和HashMap一样默认容量16,并且用数组数组实现。在ThreadLoca中通过set方法类添加数据,从源码中可以看出实际是调用了ThreadLocalMap类的set方法,我们就先来看看set方法是怎么实现的吧。

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be 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();
        }
int i = key.threadLocalHashCode & (len-1);

通过ThreadLocal的threadLocalHashCode 参数,可以理解为HashCode(int类型hash值,每个ThreadLocal对应一个),和table数组的长度-1的差做“与”运算得到元素在数组中的下标。然后从table数组中取出对应下标的Entry判断是否为null,如果没有发生冲突(取出的Entry == null)则给对应的下标赋值。如果发生了冲突(取出的Entry != null),则比较冲突的Entry的key是否和当前的set(ThreadLoacal key,Object value)的参数key相同,如果是相同的则覆盖原来的value并结束。如果参数key和获取的Entry的key不相等,这个时候就需要解决冲突,这里是通过向后移动下标,即下标 +1(这里和HashMap解决冲突不同)来解决的。

解决冲突如下:

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

那么ThreadLocalMap添加数据的过程完成了。ThreadLocal中通过get方法取出保存的数据,从源码中也可以看出实际是调用了ThreadLocalMap的getEntry方法。

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        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);
        }

getEntry先是通过int i = key.threadLocalHashCode & (table.length – 1);取得对应的下标,和前面的set方法的获取下标方式相对应。如果取到的Entry值不为null而且key也相同就返回取到的Entry。由于在添加Entry的时候有可能发生冲突,那么在取得时候就可能不能一次性通过下标取到对应的值,如果发生这样的情况就调用
getEntryAfterMiss()来获取。


        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

这里必须和前面解决冲突的思路一致,如果没有一次性取到对应的Entry,下标就向后移动(+1),然后在取出新的下标的值进行比较,如果符合条件就返回。如果取出的Entry为null,则返回null。

默认的ThreadLocalMap容量只有16,如果存放的数据多了,那么就跟HashMap一样需要扩容,默认情况下当存储的数据量超过容量的2/3的时候就会扩容为之前容量的2倍。

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
       /**
         * Double the capacity of the table.
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

相信你看到这里对ThreadLocal有更深入的理解了吧。

    原文作者:Lemon_95
    原文地址: https://www.jianshu.com/p/eeab40c59779
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞