Android源码分析之Handler源码ThreadLocal分析

123 阅读4分钟

Handler作为跨线程的消息机制,能够帮助我们实现线程间通信。相信大家都很熟悉在实际开发过程中怎么去使用,但是其内部原理却很多人没有看过,为什么Handler能够实现跨线程通信而不会出现多线程并发访问问题。下面就让我们一起探索源码,从源码中找到答案。

我们都知道在非主线程中创建使用Handler创建子线程之前必须调用Looper.prepare()。如果在主线程中使用的话就要先调用prepareMainLooper()方法。如下图:

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

    private static Looper sMainLooper;  // guarded by Looper.class
    final MessageQueue mQueue;
    final Thread mThread;
    private boolean mInLoop;

    ·········省略部分代码·········
    
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {  
          //通过threadLocal获取Looper,如果创建过了则会报错,所以perpare只能调用一次。
            throw new RuntimeException("Only one Looper may be created per thread");
        }
      //将创建好的looper保存在threadLocal中。
        sThreadLocal.set(new Looper(quitAllowed));
    }
    @Deprecated
    public static void prepareMainLooper() {//在主线程中创建Looper
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {//只能保证有唯一一个looper
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();//调用此方法返回Looper
        }
    }
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

    /**
     *返回与当前线程关联的 Looper 对象。 如果调用线程未与 Looper 关联,则返回 null。
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();//从threadLocal中获取looper
    }

此时,我们知道了looper是保存在ThreadLocal中,不管是通过Looper.prepare(),还是prepareMainLooper(),都是从Threadlocal中获取。如果有同学对looper不熟悉的话,可以查看我的上一篇文章。接下来我们好好分析一下ThreadLocal是如果保存Looper的,这个即是Handler的难点也是面试必问的知识点。

首先我们看一下ThreadLocal这个类。


public class ThreadLocal<T> {

public ThreadLocal() 
  
private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
  /*
   *返回此线程局部变量的当前线程副本中的值。 如果该变量对于当前线程没有值,
   * 则首先将其初始化为调用initialValue方法返回的值。
   * 返回:此线程本地的当前线程的值
   */
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();
    }
/**
 *将此线程局部变量的当前线程副本设置为指定值。 大多数子类将不需要覆盖此方法,仅依赖于initialValue方法来设置线程initialValue的值。
 *参数: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);
    }
/**
 *删除此线程局部变量的当前线程值。 如果此线程局部变量随后读取当前线程,
 *它的价值将通过调用其重新初始化initialValue方法,除非它的值设置在临时当前线程。
 *这可能会导致在当前线程中多次调用initialValue方法
 **/
 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

通过源码可知,ThreadLocal使用了TreadLocalMap进行保存Looper。ThreadLocalMap是一个Map,以键-值对的方式进行存储。我们先分析set方法。

public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//从当前线程中获取map
        if (map != null)
            map.set(this, value);//将当前线程作为key,存储value。
        else
            createMap(t, value);
    }

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

通过set方法我们可以看出,ThreadLocalMap是线程中的局部变量。这个就说明每一个线程中都有一个自己的局部变量ThreadLocal。并且将当前线程作为key存储Looper。然后调用prepare()方法,获取已经保存在当前线程中的Looper。

public T get() {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取线程中的Map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;//通过线程获取value
            }
        }
        return setInitialValue();
    }

现在我们知道ThreadLocal用来保存Looper,实际上是保存在每个线程中的ThreadLocalMap中,并且将当前线程作为Key。当我们通过Handler创建子线程的时候,先调用prepare()方法,来确保ThreadLocal中只有一个Looper。每个线程中都有自己的Looper,这就保证了每个线程的数据都有唯一的一份,即使是多线程的情况下,也能保证数据的唯一性。下篇文章让我们一下分析一下面试必问的消息屏障机制。