Handler消息机制(三):一个线程有几个Looper?如何保证?

一个线程中有几个Looper?

1个。

我们来到Looper初始化的地方Looper.prepare()。

     /** Initialize the current thread as a looper.



      * This gives you a chance to create handlers that then reference



      * this looper, before actually starting the loop. Be sure to call



      * {@link #loop()} after calling this method, and end it by calling



      * {@link #quit()}.



      */



    public static void prepare() {



        prepare(true);



    }



 



    private static void prepare(boolean quitAllowed) {



        //如果这个线程已经存在Looper报异常



        if (sThreadLocal.get() != null) {



            throw new RuntimeException("Only one Looper may be created per thread");



        }



        // 不存在,创建一个Looper设置到sThreadLocal



        sThreadLocal.set(new Looper(quitAllowed));



    }

那么,ThreadLocal是个什么东西呢?

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储之后,只有在指定线程中可以获取到存储的数据,对于其他的线程来说则无法获取到数据。如果不存在ThreadLocal,也可用全局的哈希表来查询,但是比较麻烦。

使用场景:

\1. 当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑使用ThreadLocal,比如Looper、ActivityThread、AMS。

\2. 当监听器的传递需要贯穿整个线程的执行过程遇到:函数的调用栈比较深,活着代码入口的多样性,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。

虽然在不同线程中访问的是同一个ThreadLocal对象,但是他们通过ThreadLocal获取到的值确实不一样的。因为不同线程访问同一个ThreadLocal的get()方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值,所以ThreadLocal可以在不同线程中维护一套数据副本且互不干扰。

ThreadLocal.set()

    /**



     * 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);



    }

先拿到当前的线程,然后拿到当前线程的一个map,如果存在这个map,直接往map里设值,如果这个map不存在,那先创建一个map,再往里面设值,ThreadLocalMap当于使用一个数组维护一张哈希表,负载因子是最大容量的2/3。

        /**



         * Set the resize threshold to maintain at worst a 2/3 load factor.



         */



        private void setThreshold(int len) {



            threshold = len * 2 / 3;



        }

下面我们进入ThreadLocalMap这个内部类里,下面的set方法实现了具体的Key和value存储,具体看看这个方法是怎么样的。

根据 key 找出 Entry 对象,如果找出的这个 Entry 的 k 等于 key,直接设置 Entry 的 value,如果 k 为空,则通过 replaceStaleEntry方法 保存数据,最后构建出 Entry 保存进 table 数组中。

        /**



         * 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();



        }

在这个方法中,有一个变量叫做table,ThreadLocal的值就存在这个table中。Entry是Map中用来保存一个键值对的,而Map实际上就是多个Entry的集合,Entry<key,value>和Map<key,value>一样的理解方式。

在Android M(Android 6.0)之前,数据结构是通过Values实现的,Values中也有一个table的成员变量,table是一个Object数组,也是以类似map的方式来存储的。偶数单元存储的是key,key的下一个单元存储的是对应的value,所以每存储一个元素,需要两个单元,所以容量一定是2的倍数。

        /**



         * The table, resized as necessary.



         * table.length MUST always be a power of two.



         */



        private Entry[] table;

此哈希映射中的条目使用它的主ref字段作为键(它总是线程本地对象)。注意空键(即entry.get() == null)表示不再引用密钥,因此条目可以从表中删除。

Entry 继承了 WeakReference<ThreadLocal>,那么通过 Entry 对象的 get 方法就可以获取到一个弱引用的 ThreadLocal 对象,Entry 保存了 ThreadLocal(key) 和 对应的值(value),其中 ThreadLoacl 是通过弱引用的形式,避免了线程池线程复用带来的内存泄露。

        /**



         * 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;



            }



        }

ThreadLocal.get()

    /**



     * 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();



    }

get()方法中,首先获取了当前的线程,然后又根据当前的线程取出map。

    /**



     * 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;



    }

这个getMap就是拿到了当前线程的ThreadLocalMap,继续回到get()方法里。

        if (map != null) {



            ThreadLocalMap.Entry e = map.getEntry(this);



            if (e != null) {



                @SuppressWarnings("unchecked")



                T result = (T)e.value;



                return result;



            }



        }

如果获取的ThreadLocalMap这个map不为空,则以ThreadLocal的引用作为Key,在map中获取对应的Entry对象;如果获取的Entry对象也不为空的话,把它的value值返回出来。

在该方法的最后一句,也就是说当map为空的时候,则直接返回这个方法的结果。

       return setInitialValue();

这个setInitialValue()方法是做什么的呢?

    /**



     * 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;



    }

首先,它先用initialValue()方法把value值获取到,然后拿到当前的线程Thread,用当前线程获取一遍ThreadLocalMap,如果这个map不是空的话,就以ThreadLocal的引用为Key,以获取到的value值为Value,往这map里设值;如果map是空的,就拿引用和value作为第一个Key和第一个Value创建一个新的map。

    /**



     * 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);



    }

通过看ThreadLocal的get()和set()方法,发现他们所操作的对象都是当前(各自)线程的LocalValues对象的table数组,他们对ThreadLocal所做的读写操作都仅限于各自的线程范围内,怪不得ThreadLocal在各自线程中可以互不干扰的对数据进行读写操作。

如何保证一个线程中只有一个Looper?

通过上面对ThreadLocal的get中看,首先获取当前线程的ThreadLocalMap,如果map为空,返回的就是setInitialValue()。

    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();



    }

如果当前线程里的ThreadLocalMap为空,就创建一个ThreadLocalMap,这个map里先存上第一个键值对(当前threadLocal为键,null为值);如果map不为空,就根据键找键值对,找到了,就返回键值对中的值,如果找不到,还是会调用setInitialValue(),存上键值对(当前threadLocal为键,null为值)。

    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;



    }

由于一个线程跟一个ThreadLocalMap是绑定的,如果这个Map中存有当前Looper里的sThreadLocal为键的键值对,就不会再存储Looper,反而而会抛异常了

    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));



    }

反之,就会存入当前Looper里的sThreadLocal为键,new Looper(quitAllowed)为值的对。

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

推荐阅读更多精彩内容