ThreadLocal在Android消息机制中的作用

作者: alighters | 来源:发表于2016-06-28 21:16 被阅读1495次

    e提及Android的消息机制,相信这是Android开发者非常熟悉,并且相当基础的一个知识点了。那这里还有什么需要讲的呢?这里,先抛出一个问题:

    问题

    我们在使用Handler的时候,都知道其必须要跟一个Looper绑定的。而在UI线程可直接初始化Handler来使用,但是在子线程则不行,系统会抛出一个必须调用Looper.prepare()的异常信息。缘由在于,当初始化Handler的时候,其会通过Looper来获取当前的Looper,代码如下:

    public Handler(Callback callback, boolean async) {
      //省略
    
      mLooper = Looper.myLooper();
      if (mLooper == null) {
          throw new RuntimeException(
              "Can't create handler inside thread that has not called Looper.prepare()");
      }
      //省略
    }
    

    那么,问题来了,为什么在子线程中,通过Looper.myLooper()方法获取的就是为空呢?如果有人回答了Looper是线程相绑定的,那它是如何做到绑定的? 如果还知道答案的话,那就可以跳过本篇文章了。

    代码分析

    1. Looper的myLooper方法

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    

    此方法只是通过从变量sThreadLocal中取出一个值。那么它的值是哪里来的呢?

    2. Looper的prepare方法

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

    可以看出的是调用了这个方法之后,会在sThreadLocal中存在一个新建的Looper对象。那么看看这个sThreadLocal是什么东西呢?

    3. sThreadLocal的定义

        // sThreadLocal.get() will return null unless you've called prepare().
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    

    是一个静态的ThreadLocal的变量,并且泛型指向的是Looper对象。另外,注释中告诉了我们,如果我们不调用prepare方法的话,那get方法返回的是null。所以现在就引出咱们的重头戏ThreadLocal了。

    ThreadLocal

    1. 定义

    先看一下官方的解释:

    Implements a thread-local storage, that is, a variable for which each thread
    has its own value. All threads share the same {@code ThreadLocal} object,
    but each sees a different value when accessing it, and changes made by one
    thread do not affect the other threads. The implementation supports
    {@code null} values.

    这段话的意思是实现了一个线程相关的存储,即每个线程都有自己独立的变量。所有的线程都共享者这一个ThreadLocal对象,
    并且当一个线程的值发生改变之后,不会影响其他的线程的值。

    2. 实现

    ThreadLocal的类定义使用了泛型ThreadLocal<T>,其中T指代的是在线程中存取值的类型。(对应Android中使用的ThreadLocal, T则存放的类型为Looper)

    • set方法
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
    

    其中的values方法如下:

    Values values(Thread current) {
      return current.localValues;
    }
    

    方法中,先通过Thread.currentThread来拿到当前线程,再拿到线程的values属性,并对此values属性进行赋值,其中key为当前的ThreadLocal对象,value则是当前要存放的值。而这个values对象,其中维持了一个一维的object数组,采用偶数为key, (索引为index)奇数为value(索引为index + 1)的数据结构。

    • get方法
    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }
    
        return (T) values.getAfterMiss(this);
    }
    

    在进行取值的时候,也是现获取当前线程,然后根据当前ThreadLocal的hash值与values的mask标志位进行与操作,来获取到当前ThreadLocal在这个线程的values中的位置,并通过判断其存放的key是不是当前ThreadLocal,若是的话,则返回index+1对应的值,即是我们所存放的值;若不是的话,则需要通过values的getAfterMiss方法来进行更进一步详细的搜索。

    总结

    ThreadLocal通过获取当前线程中的values属性,从而实现了每个单独线程的信息绑定。这样的话,Android的消息机制中,Looper便是采用ThreadLocal作为存储结构,所以looper对象的存储只会在当前线程中,子线程若是使用消息机制的话,必须调用Looper.prepare方法来在线程中新建一个Looper的对象。

    相关文章

      网友评论

      • 狂魔_ae53:楼主,有个问题,现在的values这个方法已经没用了吧,我看源码好像用ThreadLocalMap代替了
        d81d4badb4bb:你网上看的分析都是JDK包下java的实现方式
        d81d4badb4bb:@alighters 不对Android Threadlocal实现方式跟Java不一样,你讲的是Android ,他看的Java的
        alighters:是的啊

      本文标题:ThreadLocal在Android消息机制中的作用

      本文链接:https://www.haomeiwen.com/subject/kpitjttx.html