之前在分析Handler的工作原理的时候,在Looper
类中有这么一段代码,当时就不怎么理解
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
...
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));
}
这个ThreadLocal是用来干嘛的呢?下面就来说说我对ThreadLocal的理解
ThreadLocal
ThreadLocal
类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get
或set
方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。
在Handler
的运行机制中,必须保证每个线程只有一个Looper
对象,Looper
类就是利用了ThreadLocal
的特性
工作原理
我们来通过源码来分析下ThreadLocal的工作原理,我们先看下ThreadLocal有个内部类Values
,它定义了6个成员变量,分别为
//长度必须是2的n次方的值
private Object[] table;
//计算下标的掩码, 它的值是table的长度-1,待会会讲到
private int mask;
//存放进来的实体的数量
private int size;
//被删除的实体的数量
private int tombstones;
//一个阈值,用来判断是否需要进行rehash
private int maximumLoad;
//下一个要进行清理的位置点
private int clean;
我们来看一下当Values对象被创建时进行了什么工作
/**
* Constructs a new, empty instance.
*/
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}
/**
* Creates a new, empty table with the given capacity.
*/
private void initializeTable(int capacity) {
this.table = new Object[capacity * 2];
this.mask = table.length - 1;
this.clean = 0;
this.maximumLoad = capacity * 2 / 3; // 2/3
}
上面的代码我们可以看到,当初始化一个Values对象时,它会创建一个长度为capacity*2的数组。
add()
在add()方法当中,也可以看到它会把ThreadLocal对象(key)和对应的value放在连续的位置中。 也就是table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。直接通过下标存取线程变量, 它比用WeakReference类在内存占用上更经济,性能也更好。它也保证了在计算key的下标时,一定是偶数位。
void add(ThreadLocal<?> key, Object value) {
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == null) {
table[index] = key.reference;
table[index + 1] = value;
return;
}
}
}
set()
再来看看set()方法
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
//获取当前线程的ThreadLocal
Values values(Thread current) {
return current.localValues;
}
看看这里的values(currentThread)方法,其实就是取的当前线程的ThreadLocal。因为在Thread类的内容有一个成员专门用于存储线程的ThreadLocal的数据,那就是localValues
,在Thread.java
中
/**
* Normal thread local values.
*/
ThreadLocal.Values localValues;
put()
接下来看看ThreadLocal的值到底是怎么进行存储的
/**
* Sets entry for given ThreadLocal to given value, creating an
* entry if necessary.
*/
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
通过上面的存储思路,我们大概可以看出一个规则,那就是ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置,比如ThreadLocal的reference对象在table数组的索引为index,那么ThreadLocal的值在table数组中的索引就是index+1。
get()
最后在看一下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);
}
很明显,返回的也是table[index + 1]
。
使用场景
根据ThreadLocal的特性,我们可以大概可以总结它的两个使用场景
- 就是通过它在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据, 因为对于其它线程来说无法获取到数据。
- 复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂, 这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以怎么做呢? 其实就可以采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。