天天看点

Android消息机制(一)之 ThreadLocal

前言

  今天重温了Android开发艺术探索上的消息机制,花了一些时间,书上写很好,但是可能文章一些先后顺序问题,不是特别好理解,这篇文章博主用了自己的理解,看源代码,结合书上的知识,希望大家能更容易理解。(可能会写的不太好。。。不正确的地方欢迎指出)

  Android的消息机制主要是指Handler的运行机制。Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue是消息队列,用于存储一组消息(虽然叫消息队列,其实是用单链表的数据结构来存储消息列表)。Looper中文翻译为循环,这里可以理解为消息循环。它会无限循环访问消息队列中有没有新的消息需要处理。

  Looper中有个特殊的概念,就是ThreadLocal,这篇文章主要简要讲解这个ThreadLocal是什么东西,有什么用。至于Handler的运行机制具体是怎样的,后面会写关于这方面的文章。

  看完本文或者已经了解ThreadLocal的知识的,可以了解一下 MessageQueue 和 Looper 的工作原理 ,加深对消息机制的了解   Android消息机制(二)之 MessageQueue 与 Looper

   

   

Thread中存放数据

  每条线程都会存储关于那条线程的数据,或者说是跟线程有关的信息。它们都存在实例化后的线程对象中。只是有些信息是这个对象必须有的,所以它们会被定义为成员变量。而有些数据可能在运行之后,程序员突然想放进去的,那么成员变量就不能满足了。所以Thread类有一个成员变量

ThreadLocal.ThreadLocalMap

就专门用来存储这类的数据了。它是ThreadLocal类中的一个内部类,这个内部类有一个成员变量table[],用于存放数据,这个内部类提供了存储数据,获取数据的方法。

  

下面是Thread类中的成员变量

Android消息机制(一)之 ThreadLocal
Android消息机制(一)之 ThreadLocal

  

所以ThreadLocal到底是什么

  知道了上面一些前提之后,我们再来看看ThreadLocal在书上的说法:

  ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

  现在就比较好理解了,ThreadLocal其实并不是用来真正存放数据的,数据还是放在线程对象中的。我的理解ThreadLocal就像一个工具类,可以通过他的提供的方法找到运行代码所在的线程中自己想要的数据。

举个例子:

//首先定义一个ThreadLocal对象,指定存储Boolean类型数据
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>();

//然后分别在主线程、子线程1、子线程2中设置和访问它的值
    mThreadLocal.set(true);
        Log.d(TAG,"[main]mThreadLocal"+mThreadLocal.get());

        new Thread("Thread#1"){
            @Override
            public void run() {
                mThreadLocal.set(false);
                Log.d(TAG,"[#1]mThreadLocal"+mThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2"){
            @Override
            public void run() {
                Log.d(TAG,"[#2]mThreadLocal"+mThreadLocal.get());
            }
        }.start();
           

我们在主线程中设了true,在线程1中设了false,在线程2中没有设值。

那么打印出来的结果,就像预料的一样,是true,false,null

所以,上面的结果我们看到的是,虽然在不同线程中访问的是同一个ThreadLocal对象,但是它们ThreadLocal获取的值却是不一样的,这就是ThreadLocal的功能。实质上就是ThreadLocal通过运行这段代码所在的线程、和这个ThreadLocal对象作为根据,去找所对应的Thread中ThreadLocal.ThreadLocalMap中的table[]找到了我们想要的值。

(利用所在线程作为根据可以理解,可是为什么需要用到ThreadLocal的对象呢?)

  

  

ThreadLocal 中的方法

首先看ThreadLocal中的set方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
           

很简单,通过方法执行所在的线程得到了线程的ThreadLocalMap,里面又调用了ThreadLocalMap 的set方法去存放这个值,用了两个参数,一个this,代表的是当前这个ThreadLocal对象,另一个则是要存放的值。那么这个ThreadLocal对象有什么用呢?我们再看看ThreadLocalMap的set方法。

private void set(ThreadLocal key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-);
            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();
        }
           

  里面的具体实现算法就不做过多的研究了,简单的说就是用这个对象key的一些数值比如哈希码之类的形成一个标识。存放在table[]中,然后数据存放在这个标识的索引的下一个索引。简单说就是这个数组的数据就是。 标识、值、标识、值…..。

  其实很容易理解,这样我们就可以根据那个对象,去get()到我们真正想要的值了。下面是这个get()方法。

//ThreadLocal的get()
   public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
//ThreadLocalMap 的getEntry
 private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - );
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
           

ThreadLocal的应用

前面说了,Looper有用到。那么是怎么用到的呢?我们可以用Looper.myLoop()方法获取当前线程的Looper实例。这个实例就会存在线程之中,和线程绑定在一起。

public final class Looper{
  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

}
           

sThreadLocal.get()会到当前线程的ThreadLocalMap中取出这个Looper实例。不同线程取出的实例都不同。

  

  

  

—-参考文献《Android开发艺术探索》 任玉刚著