带你了解源码中的 ThreadLocal

这次想来讲讲 ThreadLocal 这个很神奇的东西,最开始接触到这个是看了主席的《开发艺术探索》,后来是在研究 ViewRootImpl 中又碰到一次,而且还发现 Android 中一个小彩蛋,就越发觉得这个东西很有趣,那么便借助主席的这次作业来好好梳理下吧。

提问

开始看源码前,还是照例来思考一些问题,带着疑问过源码比较有条理,效率比较高一点。

大伙都清楚,Android 其实是基于消息驱动机制运行的,主线程有个消息队列,通过主线程对应的 Looper 一直在对这个消息队列进行轮询操作。

但其实,每个线程都可以有自己的消息队列,都可以有自己的 Looper 来轮询队列,不清楚大伙有接触过 HandlerThread 这东西么,之前看过一篇文章,通过 HandlerThread 这种单线程消息机制来替代线程同步操作的场景,这种思路很让人眼前一亮。

而 Looper 有一个静态方法:Looper.myLooper()

通过这个方法可以获取到当前线程的 Looper 对象,那么问题来了:

Q1:在不同线程中调用 Looper.myLooper() 为什么可以返回各自线程的 Looper 对象呢?明明我们没有传入任何线程信息,内部是如何找到当前线程对应的 Looper 对象呢?

我们再来看一段《开发艺术探索》书中的描述:

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

虽然在不同线程中访问的是同一个 ThreadLocal 对象,但是它们通过 ThreadLocal 获取到的值却是不一样的。

一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。

好,问题来了:

Q2:ThreadLocal 是如何做到同一个对象,却维护着不同线程的数据副本呢?

源码分析

ps:ThreadLocal 内部实现在源码版本 android-24 做了改动,《开发艺术探索》书中分析的源码是 android-24 版本之前的实现原理,本篇分析的源码版本基于 android-25,感兴趣的可以阅读完本篇再去看看《开发艺术探索》,比较一下改动前后的实现原理是否有何不同。

因为是从 Q1 深入才接触到 ThreadLocal 的,那么这次源码阅读的入口很简单,也就是 Looper.myLopper():

//Looper#myLooper() public static @Nullable Looper myLooper() { return sThreadLocal.get(); } static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

所以,Looper.myLooper() 实际上是调用的 ThreadLocal 的 get() 方法,也就是说,Looper.myLooper() 能实现即使不传入线程信息也能获取到各自线程的 Looper 是通过 ThreadLocal 实现的。

get()

那么,下面就继续跟着走下去吧:

//ThreadLocal#get() public T get() { //1. 获取当前的线程 Thread t = Thread.currentThread(); //2. 以当前线程为参数,获取一个 ThreadLocalMap 对象 ThreadLocalMap map = getMap(t); if (map != null) { //3. map 不为空,则以当前 ThreadLocal 对象实例作为key值,去map中取值,有找到直接返回 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } //4. map 为空或者在map中取不到值,那么走这里,返回默认初始值 return setInitialValue(); }

所有的关键点就是从这里开始看了,到底 ThreadLocal 是如何实现即使调用同一个对象同一个方法,却能自动根据当前线程返回不同的数据,一步步来看。

首先,获取当前线程对象。

接着,调用了 getMap() 方法,并传入了当前线程,看看这个 getMap() 方法:

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

原来直接返回线程的 threadLocals 成员变量,由于 ThreadLocal 与 Thread 位于同一个包中,所以可以直接访问包权限的成员变量。我们接着看看 Thread 中的这个成员变量 threadLocals :

//Thread.threadLocal ThreadLocal.ThreadLocalMap threadLocals = null; //ThreadLocal#createMap() void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

Thread 中的 threadLocal 成员变量初始值为 null,并且在 Thread 类中没有任何赋值的地方,只有在 ThreadLocal 中的 createMap() 方法中对其赋值,而调用 createMap() 的地方就两个:set() 和 setInitialValue(),而调用 setInitialValue() 方法的地方只有 get()。

也就是说,ThreadLocal 的核心其实也就是在 get() 和 set(),搞懂这两个方法的流程原理,那么也就基本理解 ThreadLocal 这个东西的原理了。

到这里,先暂时停一停,我们先来梳理一下目前的信息,因为到这里为止应该对 ThreadLocal 原理有点儿眉目了

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzyfyj.html