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,这就保证了每个线程的数据都有唯一的一份,即使是多线程的情况下,也能保证数据的唯一性。下篇文章让我们一下分析一下面试必问的消息屏障机制。