Android开发之ThreadLocal原理深入理解
[Android]ThreadLocal的定义和用途
ThreadLocal用于实现在不同的线程中存储线程私有数据的类。在多线程的环境中,当多个线程需要对某个变量进行频繁操作,同时各个线程间不需要同步,此时,各个子线程只需要对存储在当前线程中的变量的拷贝进行操作即可,程序的运行效率会很高,即所谓的空间换时间。
在开源框架EventBus和android系统的Looper类当中有运用到ThreadLocal进行线程局部数据的存储,android版的ThreadLocal和java原生的ThreadLocal有一定的差别,android版的进行了一些优化设计,通过内部类Values中的Object数组来存储ThreadLocal的弱引用和线程的局部数据对象;而java版的是以MAP的方式来存储。
[Android]ThreadLocal的基本使用
ThreadLocal的使用很简单,定义一个全局的ThreadLocal对象,在线程中通过get()方法得到当前线程的局部数据进行操作。
ThreadLocal<ClassType> storageDataThreadLocal = new ThreadLocal<ClassType>(){
@Override
protected ClassType initialValue() {
return new ClassType();
}
};
ClassType为自定义的数据类,实例化ThreadLocal对象过程中如果没有重写initialValue方法则第一次调用get方法会返回空,这里需要注意。
[Android]ThreadLocal实现线程数据拷贝的原理
我们关注最多的应该就是ThreadLocal如何实现线程的局部数据存储,通过分析源码可以得到最终的答案。
我们直接分析get函数:
/**
* Returns the value of this variable for the current thread. If an entry
* doesn't yet exist for this variable on this thread, this method will
* create an entry, populating the value with the result of
* {@link #initialValue()}.
*
* @return the current value of the variable for the calling thread.
*/
@SuppressWarnings("unchecked")
public T get() {
// Optimized for the fast path.
//通过native方法得到代码当前执行的线程对象
Thread currentThread = Thread.currentThread();
//得到当前线程的Values类型对象
Values values = values(currentThread);
if (values != null) {
//得到对象数组table,用来存储当前ThreadLocal对象的弱引用
//和当前线程的局部数据对象引用
Object[] table = values.table;
//得到用于存储ThreadLocal弱引用的散列值索引
int index = hash & values.mask;
//指向到对象相同,则返回其引用
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
//新建values对象赋给当前线程对象的localValues引用
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
如果当前线程对象内的Values对象中有存储和当前ThreadLocal对象的reference属性是同一个对象的引用则返回此线程的本地数据,即变量在线程的本地拷贝。
否则在当前线程内部创建一个Values对象,通过Values.getAfterMiss将ThreadLocal类的initialValue方法得到的本地数据存储到当前线程对象内部并返回;initialValue是protected方法,如果子类没有重写,则默认返回空。
静态内部类Values的getAfterMiss函数:
/**
* Gets value for given ThreadLocal after not finding it in the first
* slot.
*/
Object getAfterMiss(ThreadLocal<?> key) {
Object[] table = this.table;
//通过散列算法得到ThreadLocal的first slot的索引值
int index = key.hash & mask;
// If the first slot is empty, the search is over.
if (table[index] == null) {
//如果first slot上没有存储 则将ThreadLocal的弱引用和本地数据
//存储到table数组的相邻位置并返回本地数据对象引用
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().
//遍历table数组,根据不同判断将ThreadLocal的
//弱引用和本地数据对象引用存储到table数组的相应位置
put(key, value);
return value;
}
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
// Continue search.
for (index = next(index);; index = next(index)) {
Object reference = table[index];
if (reference == key.reference) {
return table[index + 1];
}
// If no entry was found...
if (reference == null) {
Object value = key.initialValue();
// If the table is still the same...
if (this.table == table) {
// If we passed a tombstone and that slot still
// contains a tombstone...
if (firstTombstone > -1
&& table[firstTombstone] == TOMBSTONE) {
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
// No need to clean up here. We aren't filling
// in a null slot.
return value;
}
// If this slot is still empty...
if (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;
}
if (firstTombstone == -1 && reference == TOMBSTONE) {
// Keep track of this tombstone so we can overwrite it.
firstTombstone = index;
}
}
}
getAfterMiss函数根据不同的判断将ThreadLocal的弱引用和当前线程的本地对象以类似MAP的方式,存储在table数组的相邻位置,其中其散列的索引hash值是通过hashCounter.getAndAdd(0x61c88647 * 2)算法来得到。
set函数:
/**
* Sets the value of this variable for the current thread. If set to
* {@code null}, the value will be set to null and the underlying entry will
* still be present.
*
* @param value the new value of the variable for the caller thread.
*/
public void set(T value) {
Thread currentThread = Thread.currentThread();
//得到当前线程的本地Values对象
Values values = values(currentThread);
if (values == null) {
//为空则新建Values对象
values = initializeValues(currentThread);
}
//将当前ThreadLocal对象引用和本地数据存储到
//当前ThreadLocal的内部Values对象的table数组当中
//当调用get方法时,获得的即是当前线程的本地数据
values.put(this, value);
}
[Android]关于ThreadLocal内存泄漏的问题
在其他的博文中有说到,java版的ThreadLocal“value值因为存在一条从current thread连接过来的强引用,只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.”.
在android版的ThreadLocal中value值不存在强引用,故在table数组中的指定位置置为null,本地数据则没有存在指向其的引用,当GC回收的时候会清理掉。
[Android]ThreadLocal总结
ThreadLocal实现线程本地存储的原理是比较清晰的,即在当前线程中调用get方法时,通过ThreadLocal的initialValue方法创建当前线程的一个本地数据拷贝,将此拷贝添加到当前线程本地数据的table数组当中;或者在调用set方法时,将当前线程的本地数据存储到当前线程的table数组中.当前线程通过调用ThreadLocal对象的get方法即得到当前线程本地数据对象。