Android中ThreadLocal有一个典型应用场景,存取主线程线程中的Looper对象,例如:在主线程中调用Looper.prepare(),与在子线程中调用Looper.prepare()初始化是不同线程的Looper对象。
那么,ThreadLocal是什么呢?ThreadLocal通常称为“线程局部变量”,也就说某些数据是以线程为作用域,在不同线程中有不同的数据副本。说到作用域,作为对比,方法里的局部变量作用域是方法体,其他方法无法访问。简单来说,希望在
指定线程中存储数据,并在取出
指定线程中数据,但其他线程不可访问该数据。
ThreadLocal是Java提供的原生APi,并不是Android特有的。同时Android5.0对ThreadLocal进行一些优化设计,与原生还是有区别的。本篇文章是为下篇分析Handler机制的工作流程做准备,但不准备涉及Looper相关的讲解,只对ThreadLocal在Android中表现进行分析。
第10行,主线程中设置数据为"ActivityThread ... " ;
从结果来看,在不同线程中调用ThreadLocal$get方法获取的值并不同。由于子线程没有调用ThreadLocal$set方法,取出的值是null,这是为什么呢,在后面的分析中会给出答案。
三,ThreadLocal的工作原理
下面以Android5.0的ThreadLocal的源码来分析,首先看ThreadLocal的构造函数,源码如下:
/**
* Creates a new thread-local variable.
*/
public ThreadLocal() {}
它是一个空实现,什么也没做。
ThreadLocal存储数据,会调用set方法,查看
ThreadLocal$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;
}
//继续查看
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
第2行,获取当前线程对象,在上述例子中指的是ActivityThread,也就是主线程;
第3行与第12行结合看,返回Thread类的localValues字段;该字段在Thread类中定义:ThreadLocal.Values localValues;
第5行,如果values为null,即localValues为null,则调用initializeValues方法;
第18行,initializeValues方法里面创建了一个Values对象,并初始化字段localValues。Values类是ThreadLocal的一个内部类,在java原生的ThreadLocal中,代替Values类的是ThreadLocalMap类。由上面可知,每一个Thread都会先初始化localValues字段,也即创建一个该线程的Values对象,每个线程的Values对象都是不同的,于是
ThreadLocal可以在不同的线程中互不干扰的存储,查询数据。
第7行,调用内部类Values$put方法,存储数据。
查看ThreadLocal$Values$put方法源码:
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;
}
}
}
第9行,table是一个Object对象数组,定义:private Object[] table;
第11行,reference是ThreadLocal的一个字段,定义:private final Reference> reference = new WeakReference>(this),用弱引用对ThreadLocal进行包装。
第13,21,28行,将ThreadLocal的弱引用和需要存储的数据value放在table数组的相邻位置,形成一种映射关系,reference的索引位置加1就是value的索引位置。
从上面的分析可知,在当前的线程的Values对象中,维护了一个Object对象数组,并将ThreadLocal的弱引用与需要存储的数据,存放在数组的相邻位置。
ThreadLocal查询数据,会调用get方法,查看
ThreadLocal$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);
}
第4行,获取当前线程的Values对象;
第8行,若ThreadLocal的弱引用在table数组中的索引位置是index,继续往下执行;
第9行,table数组索引位置为index + 1中存储了需要查询的数据,return该数据;
第12行,创建当前线程的Values对象,并赋值给Thread的字段localValues;
第15行,若当前线程的Values对象为空时,也就是未调用set方法存储值时,调用Value$getAfterMiss方法并return,下面会继续分析这里。
查看ThreadLocal$Value$getAfterMiss方法源码:
Object getAfterMiss(ThreadLocal> key) {
Object[] table = this.table;
int index = key.hash & mask;
// If the first slot is empty, the search is over.
if (table[index] == null) {
Object value = key.initialValue();
// If the table is still the same and the slot is still empty...
if (this.table == table && table[index] == null) {
table[index] = key.reference;
table[index + 1] = value;
size++;
cleanUp();
return value;
}
// The table changed during initialValue().
put(key, value);
return value;
}
//...code
}
//继续查看...
protected T initialValue() {
return null;
}
第7行,调用ThreadLocal$initialValue方法,并赋值给变量value;
第21行,return value;
第30行,initialValue方法修饰符是protected,也就是它希望ThreadLocal子类来重写这个方法。
第31行,initialValue方法返回null;
也就是说,如果当前线程中没有调用ThreadLocal$set方法存储数据时,调用ThreadLocal$get方法查询数据会返回null。这也解释前面打印的log中,子线程为什么是null。当然,可以在创建ThreadLocal子类实例时,重写initialValue方法。
将上述示例中创建ThreadLocal实例的代码,修改如下:
mThreadLocal = new ThreadLocal(){
@Override
protected String initialValue() {
return "initialValue ... ";
}
};
运行,打印log如下:
12-05 09:32:49.111: E/wcc(7850): 主线程 : ActivityThread ...
12-05 09:32:49.131: E/wcc(7850): 子线程 : initialValue ...
四,最后
本篇文章先是通过一个简单的示例展示ThreadLocal的使用,可以初步了解ThreadLocal想要完成是什么样的效果。后面通过ThreadLocal的set,get方法来阐述ThreadLocal的工作原理。简单来说,ThreadLocal可以在不同线程(作用域)中,线程间互不干扰的存储和查询数据。
Android中Looper在不同线程中表现为不同的Looper对象,同时创建Handler对象时,会检查该线程中是否创建了Looper对象。那么,如何确定某一线程中是否创建Looper呢,使用ThreadLocal的特性可以很方便实现当前线程中Looper的存取操作。
值得一提的是,ThreadLocal声明的泛型是T类型,相当于Object类型。本示例中存取的String类型数据,若同时想存取其他类型的数据,需要创建一个新的ThreadLocal对象。一个ThreadLocal对象,只能存取一种类型的数据,并在不同线程中有不同的数据副本。