深入理解ThreadLocal

本文以android-26的源码为基础进行分析

Looper与ThreadLocal

首先我们从Looper的源码开始
在使用Handler和Looper的时候,我们知道,Handler发送消息给Looper,加入其消息队列,Looper则不断循环去除队列前端的消息,并执行Handler中的回调代码。

但是Looper在使用前需要先执行Looper.prepare()操作,否则会报如下的错误: Can't create handler inside thread that has not called Looper.prepare(),因为此时Looper对象并没有初始化

那么我们来看一下Looper.prepare()做了什么

public final class Looper {

   public static void prepare() {
        prepare(true);
       }

    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.set(new Looper(quitAllowed)) 这一句代码我们发现, Looper.prepare()当中new了一个Looper对象,并将这个Looper对象设置进了一个叫做的东西里去,我们想要取当前线程的Looper对象的话,可以直接调用Looper.myLooper()这个静态方法。

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

来看一下sThreadLocal的定义:

public final class Looper {

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

我们发现这个sTheadLocal是Looper中的一个静态对象,类型为ThreadLocal,接下来我们需要解释一下ThreadLocal是个什么东西

ThreadLocal

ThreadLocal的一个特点就是线程间互相隔离,在每个线程取同一个ThreadLocal对象都会得到一个不同的值。例如Looper, 每个线程的都只有一个Looper,且在各个线程中调用Looper.myLooper()的方法都会返回其自己的Looper。

在上面我们看到Looper.myLooper()方法通过调用sThread.get()方法获取到了自己对应的Looper,那么我们来看一下ThreadLocal的get()的方法。

public class ThreadLocal {

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

ThreadLocal中的get()方法首先获取当前的thread对象,然后得到了当前Thread对象中的ThreadLocalMap对象,以当前的ThreadLocal对象为key获取到ThreadLocalMap存的value,在这个例子中这个value就是Looper啦。

暂停一下,咱们捋一捋,首先Looper通过其中的静态变量sThreadLocal.get()方法获取到当前线程的ThreadLocalMap对象threadLocals 。然后又以sThreadLocal这个对象本身为key取到了threadLocals存着的Looper对象。

看着好像有点绕,那么我们换一种说法,每个Thread都存了一个map(实际是个数组),可以以Looper中的静态对象sThreadLocal为key,在这个map中取到这个线程的Looper。由于sThreadLocal是个静态对象,所以对于任意一个线程,这个key是固定的,这样一来,通过Thread.currentThread()获取到了某个线程就等于获取到了其中的Looper对象。

这么一来是不是就清晰了很多,Looper中之所以要定义这么一个sThreadLocal的静态对象,实际上就相当于定义了一个全局存在的static final的key,只不过这个key是ThreadLocal对象而已。

/** 本文不会对ThreadLocalMap进行更深入的分析,各位可以自行阅读源码去查看其具体实现**/

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

最后我们稍微圆一圆整个流程,现在我们知道了我们获取到了一个Thread,就能以sThreadLocal为key get到其中的Looper对象。那么我们是什么这个Looper对象set到对应的Thread当中的呢。记不记得文章开头Looper.prepare()的过程,其中调用了sTheadLocal.set(T)方法,最后是怎么将Looper对象绑定到线程上的,来看一下源码:

public class ThreadLocal {

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

首先获取到当前线程t,然后得到其中的ThreadLocalMap对象,然后将这个Looper对象放到了map中对应sThreadLocal的地方。相信这里大家都能很轻松的理解了。

最后总结一下ThreadLocal存在的意义:
  • 封装了对当前所在线程中ThreadLocalMap的set和get操作,默认以自身作为key值进行数据存储
  • 以泛型对外暴露,支持了所有数据类型

因此在使用ThreadLocal的时候,每一个数据都需要新建一个ThreadLocal对象为key
例如针对每一个线程都要存储一个String,需要新建一个ThreadLocal对象,如果希望在任何地方都能够获取到这个String,则该ThreadLocal对象需要是用static进行修饰。

ThreadLocal.png

你可能感兴趣的:(深入理解ThreadLocal)