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)为值的对。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容