这两天在对android的消息机制(handler)进行深入的了解和学习,在研究相应的源码的过程中,发现在Looper中被使用的ThreadLocal,对于它的作用出于好奇便进行了一些比较简单的分析,现在将我的学习心得公布如下:
ThreadLocal这个类,相信对于之前从来都没有接触过这个类的程序猿来说,也许会把它认为是一个线程类。其实不然,它的作用可以大致理解为在各个线程中用来存储数据。需要举例来进行说明的话,那可以以Handler为例来进行说明:Handler需要获取当前线程中的Looper对象,但是不同的线程中含有不同的Looper对象,这个时候使用ThreadLocal对Looper进行保存,那就实现了在不同的线程中读取到的Looper对象就是相应的那个线程中的。
ThreadLocal在android源码中的Looper、ActivityThread以及AMS中都有涉及。概括来说,ThreadLocal是一个线程内部的数据存储类,通过它就可以在指定的线程中存储数据,然后只有在这个指定的线程中才能够访问得到之前存储的数据。但是对于其他线程来说,是获取不到保存在另外线程中的数据的。一般来说,当某些数据是以线程为作用域并且不同的线程对应着不同的数据副本的时候,就可以考虑使用ThreadLocal了。下面通过一个代码示例来说明其作用。
public class ThreadLocalTestAct extends Activity {
private static final String TAG = "ThreadLocalTestAct";
private ThreadLocal<String> mThreadLocal = new ThreadLocal<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_local_test);
//main thread
mThreadLocal.set("this is thread=======" + Thread.currentThread().getName());
Log.d(TAG, "mThreadLocal 's value=======" + mThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
super.run();
mThreadLocal.set("this is thread=======" + Thread.currentThread().getName());
Log.d(TAG, "mThreadLocal 's value=======" + mThreadLocal.get());
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
super.run();
Log.d(TAG, "mThreadLocal 's value=======" + mThreadLocal.get());
}
}.start();
}
}
代码比较简单,执行之后的结果如下:
通过上述的小Demo,我想读者肯定是明白了ThreadLocal的作用了。而它之所以有这样的结果就是因为不同的线程访问同一个ThreadLocal的get方法,ThreadLocal会从各自线程中抽出一个数组,然后再从数组中根据当前的ThreadLocal的索引去查找出对应的value值。结果可知,不同的线程中的数组肯定是不同的,这也就是为什么通过ThreadLocal可以在不同的线程中维护一套数据而且还相互不干扰的原因了。
如果从源码的角度上来简要分析的话,我们可以来简单的看一下ThreadLocal的源码,最主要的就是这个set方法和get方法了吧。
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(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
/**
* 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;
}
}
}
以上对于set方法来说,其源码中的Values类的作用是什么呢?其实在Thread类的内部有一个成员专门来存储线程的ThreadLocal类的数据,这样的话,获取当前线程中的ThreadLocal的数据就很简单了,如果values的值为空,那就实例化这个对象,之后再对相应的数据进行存储。第13行的table变量就是我们的说的存储数据的数组了,我们可以发现ThreadLocal的值在table数组中的存储位置总是被固定在ThreadLocal的reference字段所代表对象的后一位。这样我们就知道了其数据在内部被存储的过程了,对于get方法也比较好了解了。
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.
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);
}
我们可以看到还是先得到当前thread的values对象,如果该对象为空的话,那就初始化,之后返回值即可(当然我们可以明显知道返回值是null);如果values值不空,那就将table数组中的reference字段之后的那个数据项返回,这也就印证了上面所说的ThreadLocal将存储的数据放在table数组中reference字段之后的结论。