前言
这一阵子一直在看Picasso,在看的过程中发现了很多很有意思的东西,有的是以前见过甚至用过但是没有深入关注的,有些是以前根本没有见过的——比如今天要讲的ThreadLocal。(android 6.0)
正文
1,ThreadLocal是什么?
先看一下Android官网的文档:
Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLocal object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports null values.
实现了每个线程的自有变量的存储。所有线程共享同一个ThreadLocal对象,但是每个线程只能访问自己所存储的变量,并且线程之间做的改动互不影响。此实现支持null变量的存储。(非逐词翻译)
通过这个描述,我们可以知道一些信息:
- ThreadLocal是一个线程的内部存储类,通过它我们可以在指定的线程中存储信息。
- 每个线程之间的信息是独立且封闭的。
但是同时也会有一些疑问:
- 所谓的自有变量是什么?
- 通过什么形式存储?
- 怎么对存储的信息进行操作?
- 怎么实现的线程间的信息封闭且独立?
- 等等
接下来我们就慢慢的在探索这个类的过程中,来从头到尾的弄清楚这些个疑问。
2,ThreadLocal的用途
ThreadLocal的作用是实现每个线程的自有变量的存储,这个“自有变量”具体是什么当然要根据不同的需求来定,但是在Android的源码中,这个自有变量常常是Looper。
这个Looper是什么呢?这里就不得不提到Android的消息机制了。Android的消息机制是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。简单来讲,就是Handler发送Message到MessageQueue中,而Looper不断的从MessageQueue中循环摘取Message,并进行进一步的解析处理。
但是Looper只是个简单的类而已,它虽然提供了循环处理方面的成员函数loop(),却不能自己凭空地运行起来,而只能寄身于某个真实的线程。那么Looper是怎么和线程建立联系的呢?这个时候ThreadLocal就参与其中了。
我们来看一下Looper的部分源码:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//调用ThreadLocal的set()方法将Looper存进去
sThreadLocal.set(new Looper(quitAllowed));
}
public static void loop() {
//调用myLooper()方法得到Looper,我们接着看一下myLooper()方法
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
…… ……
}
public static @Nullable Looper myLooper() {
//调用ThreadLocal的get()方法得到刚才存入的Looper对象
return sThreadLocal.get();
}
关于ThreadLocal的set()和get()方法是怎么对Looper进行操作并将其存入Thread中的下文会有详述。在这里我们可以清楚的看到ThreadLocal的踪迹并对它的用途有一个比较清晰的认识:当某个变量是与线程相关的并且不同线程具有不同值的时候,我们就可以考虑使用ThreadLocal这个类来简化我们的工作(比如Looper)。
更多ThreadLocal的具体用途大家可以看下任教主的这篇博文:Android的消息机制之ThreadLocal的工作原理
3,ThreadLocal用法
它的用法其实挺简单的,暴露出来的方法一共只有三个:
- get():返回调用线程中变量的当前值
- set(T value):设置调用线程中变量的值
- remove():移除调用线程的当前变量
用的话也是这三个方法,就跟上面Looper里面的用法差不多。例子的代码如下:
mIntegerThreadLocal = new ThreadLocal<>();
mIntegerThreadLocal.set(0);
//在主线程里设置mIntegerThreadLocal的值为0,输出0
Log.d(TAG, "[Thread#main]mIntegerThreadLocal=" + mIntegerThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
//设置mIntegerThreadLocal的值为1,输出1
mIntegerThreadLocal.set(1);
Log.d(TAG, "[Thread#1]mIntegerThreadLocal=" + mIntegerThreadLocal.get());
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
//不设置任何值,输出null
Log.d(TAG, "[Thread#2]mIntegerThreadLocal=" + mIntegerThreadLocal.get());
}
}.start();
new Thread("Thread#3") {
@Override
public void run() {
//设置为3,然后remove,输出null
mIntegerThreadLocal.set(3);
mIntegerThreadLocal.remove();
Log.d(TAG, "[Thread#3]mIntegerThreadLocal=" + mIntegerThreadLocal.get());
}
}.start();
输出结果如下:
05-08 16:08:24.771 14423-14423/com.lypeer.apifinder D/MainActivity: [Thread#main]mIntegerThreadLocal=0
05-08 16:08:24.774 14423-14447/com.lypeer.apifinder D/MainActivity: [Thread#2]mIntegerThreadLocal=null
05-08 16:08:24.780 14423-14446/com.lypeer.apifinder D/MainActivity: [Thread#1]mIntegerThreadLocal=1
05-08 16:08:24.781 14423-14448/com.lypeer.apifinder D/MainActivity: [Thread#3]mIntegerThreadLocal=null
事实证明,ThreadLocal确实做到了官方文档里说到的功能:存储Thread的信息,并且每个线程之间信息独立,即它们只能访问自己对应的信息,并且修改自己对应的信息之后对其他线程没有影响。
4,源码解析
4.1,构造方法
先看一下它的构造方法:
public ThreadLocal() {}
可以的,啥都没干,空构造,这条路子走不通。
4.2,暴露方法
4.2.1,public void set(T value)
接下来从暴露出来的方法入手,先看set()方法: