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有更深入的理解了吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容