这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战
前言
看过Android启动流程的源码或者Handle的源码,实现原理,应该都知道ThreadLocal,因为它是消息机制中用来存储线程内Looper的,针对线程间数据的读取。
一、简单复习下Handler的消息机制原理
- Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。
- MessageQueue的中文翻译是消息队列,顾名思义它的内部存储了一组消息,其以队列的形式对外提供插入和删除的工作,虽然叫做消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。
- Looper的中文翻译为循环,在这里可以理解为消息循环,由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。
- Looper中还有一个特殊的概念,那就是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。大家知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?
- 这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。
- 当然需要注意的是,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。大家经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
二、ThreadLocal
ThreadLocal --> ThreadLocalMap(Entry[] table) ,它提供了set()和get()方法;
1)ThreadLocal 是什么
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。
在日常开发中用到ThreadLocal的地方较少,但是在某些特殊的场景下, 通过ThreadLocal可以轻松地实现一些看起来很复杂的功能,这一点在Android的源码中也有所体现,比如Looper、ActivityThread以及AMS中都用到了ThreadLocal。 具体到ThreadLocal的使用场景,这个不好统一地来描述,一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取,如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。
2)ThreadLocal的实现方式
ThreadLocal的类定义使用了泛型ThreadLocal,其中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(Thread current) {
return current.localValues;
}
set方法中,先通过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);
}
get方法在进行取值的时候,也是现获取当前线程,然后根据当前ThreadLocal的hash值与values的mask标志位进行与操作,来获取到当前ThreadLocal在这个线程的values中的位置,并通过判断其存放的key是不是当前ThreadLocal,若是的话,则返回index+1对应的值,即是我们所存放的值;若不是的话,则需要通过values的getAfterMiss方法来进行更进一步详细的搜索。
3)Handler中ThreadLocal的使用
ThreadLocal通过获取当前线程中的values属性,从而实现了每个单独线程的信息绑定。这样的话,Android的消息机制中,Looper便是采用ThreadLocal作为存储结构,所以looper对象的存储只会在当前线程中,子线程若是使用消息机制的话,必须调用Looper.prepare方法来在线程中新建一个Looper的对象。
当我们在一个线程中使用Looper的时候,需要调用Looper的prepare方法和loop方法,不然就会出现异常。简单看下Threadlocal存储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));
}
loop方法内部先调用了myLooper方法:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以很清楚的看到,Looper 的prepare方法先创建Looper,并使用ThreadLocal存储即与当前的线程进行关联,然后loop方法开启消息机制的时候,使用ThreadLocal方法获取到当前线程的Looper。
总结
ThreadLocal是一种针对线程间数据副本不同的巧妙设计,开发者无需理会内部的复杂实现,只需在调用的时候使用get和set方法即可!对外屏蔽了细节,是一种设计思想的体现。