一个线程中有几个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设置到sThreadLocalsThreadLocal.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);elsecreateMap(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,那么通过 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);elsecreateMap(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);elsecreateMap(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)为值的对。

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

  1. Handler消息机制(二):一个线程有几个Handler

    在消息机制里面,有一个非常重要的东西,那就是Looper,Looper的作用主要是从消息队列里面取出消息交给Handler处理,不过不仅限于此,在这里面还有很多东西值得我们去源码看一看: 1.从Loo ...

  2. 【Android】线程间通信——Handler消息机制

    文章目录 引言 Java层 永动机跑起来 示例 Looper Handler MessageQueue 永动机停下 Native层 nativeInit() nativePollOnce() nati ...

  3. Android Handler消息机制源码分析

    一,前言 众多周知, Android 只允许在主线程中更新UI,因此主线程也称为UI线程(ActivityThread). 如此设计原因有二: (1) 由于UI操作的方法都不是线程安全的,如果多个线程 ...

  4. Android进阶知识树——Android Handler消息机制

    1.概述 在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答 ...

  5. handler消息机制入门

    handler消息机制入门 为什么要用handle? 我们在网络上读取图片信息时,是不能把耗时操作放在主线程里面的,当我们在子线程中获取到了图片的消息的时候,我们就需要把这个数据传给主线程. 而直接使 ...

  6. Android Handler消息机制不完全解析

    1.Handler的作用 Android开发中,我们经常使用Handler进行页面的更新.例如我们需要在一个下载任务完成后,去更新我们的UI效果,因为AndroidUI操作不是线程安全的,也就意味着我 ...

  7. Handler消息机制详解

    Handler机制是Android开发中最常见的机制,可以说贯穿整个Android,在探究Handler机制原理之前,我们先来捋一下用法 1.handler.post(Runnable) 2.hand ...

  8. Android Handler消息机制源码解析

    好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...

  9. Android Framework学习(八)之Handler消息机制(Native层)解析

    在深入解析Android中Handler消息机制一文中,我们学习了Handler消息机制的java层代码,这次我们来学习Handler消息机制的native层代码. 在Java层的消息处理机制中,Me ...

最新文章

  1. Fertility of Soils:根系C P计量比影响水稻残根周际酶活的时空动态分布特征
  2. ASA防火墙学习笔记1-基础篇
  3. php原码初级自定义数据库操作方法
  4. (HDU)1091 --A+B for Input-Output Practice (III)(输入输出练习(III))
  5. Shift+F5后,vc6不可以退出调试状态,程序不能再次执行
  6. 三分钟构建高性能 WebSocket 服务 | 超优雅的 SpringBoot 整合 Netty 方案
  7. 并发基础(八) java线程的中断机制
  8. python数据分析图表展示_1行代码实现Python数据分析:图表美观清晰,自带对比功能丨开源...
  9. 使用JOTM实现分布式事务管理(多数据源)
  10. 加密货币的时代,真的来临了吗?
  11. JS函数调用的四种方法
  12. Flask 中的蓝图 Blueprint
  13. MySQL学习笔记—复制表
  14. windows installer 3.2搞定
  15. 员工30年换150万补偿款!佳能珠海关厂 因给太多遭痛骂:恶意拉高赔偿标准
  16. Redmi K40游戏增强版外观配置前瞻:价格成唯一悬念!
  17. 正则表达式 相关教程
  18. 从本科到研究生,看大疆工程师给你定制的机器人学习计划
  19. DDOS攻击已然渗透互联网和物联网
  20. cJSON库的使用(一)

热门文章

  1. Php中如何记录本报时间,详细讲解PHP的日期时间函数date()
  2. 位宽512bit显卡_显卡知识:关于显卡位宽的基础知识科普
  3. 如何把主机系统上传到服务器,主机系统上传到服务器
  4. 英特尔的指令集体系结构_对标英特尔的RISC-V大有可为,CPU三分天下格局可期
  5. python程序保存_初识python 文件读取 保存
  6. Chrome检查更新总失败?安装细则讲解
  7. [Dynamic Language] Python3.7 源码安装 ModuleNotFoundError: No module named '_ctypes' 解决记录...
  8. Java程序的运行原理及JVM的启动是多线程的吗?
  9. Java中的集合笔记
  10. 线程或进程绑定到特定的cpu