Handler的前世今生1——ThreadLocal

1.简要

有些初级开发者总是认为Handler是用来更新UI的。这从其主要使用场景上讲,是没有问题的。但是要想更好的去了解Handler及其背后的运行机制,这个认识往往会导致对Handler理解的不够深刻,可谓是一叶障目,不见泰山。(PS:我在面试过程中,经常会考察面试者对于Handler的认识)

片面认识—— Handler是用来更新UI的。


2. 面试迷茫点

No Looper; Looper.prepare() wasn’t called on this thread.

Only one Looper may be created per thread

上面这两个异常,有些开发者可能在实际开发工作中都没有遇到过。究其原因,没有在子线程中去创建过Handler


这也是有很大一部分面试者,谈到Handler的运行机制,用法及注意事项时,能做到侃侃而谈。下面列出几种常见场景:

  1. 当谈起多线程通信时,有一些面试者不知所云,但是说Handler,立刻就精神焕发。
  2. 涉及到HandlerThread的存在意义,有些茫然无措。
  3. 涉及到Handler,Thread,Looper,MessageQueue之间的对应关系时,有些说不清楚。

个人认为,这都是对于Handler的片面认识导致的。


3. Thread 和 Looper的婚姻关系

废话一句,我还是希望大家能仔细看一下上面的文字…

No Looper; Looper.prepare() wasn’t called on this thread. 不存在Looper——在该线程中没有调用Looper.prepare()方法。

Only one Looper may be created per thread 一个线程只能创建一个Looper。

从上面的错误提示中,我们可以得出一个结论:线程和Looper是1:1对应的关系

我们今天这篇文章主要探讨的是:如何保持这种正常的婚姻关系

在Looper类中,你会看到:

ThreadLocal成员变量
 // sThreadLocal.get() will return null unless you've called prepare().
 // 若不调用prepare()方法则返回null
    @UnsupportedAppUsage(只支持APP使用)
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
prepare()方法
// 清楚地看到该变量的set()/get()
 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 注意这里:新建Looper,我们看看Looper构造函数干了些什么
        sThreadLocal.set(new Looper(quitAllowed));
    }
Looper方法
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

是谁在努力维持着这段美好的婚姻(Looper 和 Thread )呢?

Oop, ThreadLocal.


3.浅识ThreadLocal

1. set()方法
 /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value. 给当前线程设置一个指定的值。
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

key-value形式:当前线程为key,value就是我们传过来的值。这唯一性就保持住了。So easy…

看见这个ThreadLocalMap,每个人都会猜测,它难道是一个HashMap 或者HashTable,抑或是 CocurrentHashMap。走过去看一下吧。

	/*ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values
     * /
  static class ThreadLocalMap {
  }

ThreadLocalMap是一个只适用于维护thread local 的自定义键值对。

4. 总结:

  1. Looper 和Thread 是 1V1的关系,其由ThreadLocal来维持关系;
  2. ThreadLocal 内部是由ThreadLocalMap来存储的。

每个线程(Thread对象)都有一个ThreadLocalMap实例,ThreadLocalMap是ThreadLocal的一个内部静态类。
ThreadLocalMap是一个自定义的哈希表实现,用于存储键值对,其中Key 是ThreadLocal对象,Value是线程局部变量。

欢迎继续收看 :Handler的前世今生2——Looper

是的,`Handler` 的底层确实使用了 `ThreadLocal`,主要是为了实现每个线程拥有独立的 `Looper` 实例,从而保证线程安全。 在 Android 中,每个线程的 `Looper` 是通过 `ThreadLocal` 来存储和获取的。`Handler` 在发送和处理消息时依赖于当前线程的 `Looper`,而 `Looper` 的绑定机制正是通过 `ThreadLocal` 实现的。 下面是 `Looper` 类中使用 `ThreadLocal` 的关键代码(简化版): ```java public final class Looper { // 使用 ThreadLocal 保存每个线程的 Looper 实例 private static ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>(); // 当前线程的 MessageQueue final MessageQueue mQueue; private Looper() { mQueue = new MessageQueue(); } public static void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); } public static Looper myLooper() { return sThreadLocal.get(); } public static void loop() { final Looper me = myLooper(); final MessageQueue queue = me.mQueue; // 进入消息循环 for (;;) { Message msg = queue.next(); // 可能会阻塞等待 if (msg == null) return; msg.target.dispatchMessage(msg); msg.recycle(); } } } ``` ### 总结 - `Handler` 的底层依赖于 `Looper`,而 `Looper` 是通过 `ThreadLocal` 实现线程本地存储的。 - 这样确保了每个线程都有自己的 `Looper` 实例,`Handler` 才能正确地发送和处理消息。 - 这种设计避免了线程间共享 `Looper` 带来的并发问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值