Android消息机制之线程间存储ThreadLocal源码分析
概述
ThreadLocal是一种线程内部存储类。通过他存储的数据在不同的线程操作而互不干扰,类似于各个线程中都有一份自己的copy数据做处理。正如源码中的注解解释:
This class provides thread-local variables.
Android消息机制中就用到了该类来存储每个线程的Looper。
概念可能有些抽象,下面举一个简单的例子:
private final String TAG = "MainActivity";
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
threadLocal.set("mainThread");
Log.d(TAG, "this is mainThread ,threadLocal = " + threadLocal.get());
new Thread("Thread_1") {
@Override
public void run() {
super.run();
Log.d(TAG, "this is Thread_1 ,before calling set method , threadLocal = " + threadLocal.get());
threadLocal.set("Thread_1");
Log.d(TAG, "this is Thread_1 ,after calling set method , threadLocal = " + threadLocal.get());
}
}.start();
new Thread("Thread_2") {
@Override
public void run() {
super.run();
Log.d(TAG, "this is Thread_2 ,before calling set method , threadLocal = " + threadLocal.get());
threadLocal.set("Thread_2");
Log.d(TAG, "this is Thread_2 ,after calling set method , threadLocal = " + threadLocal.get());
}
}.start();
}
上述实例代码中在MainThread中给ThreadLocal赋值了“mainThread”,并打印结果。然后在“Thread_1”和“Thread_2”中打印输出ThreadLoal的值,再重新赋值打印。输出结果如下:
this is mainThread ,threadLocal = mainThread this is Thread_2 ,before calling set method , threadLocal = null this is Thread_2 ,after calling set method , threadLocal = Thread_2 this is Thread_1 ,before calling set method , threadLocal = null this is Thread_1 ,after calling set method , threadLocal = Thread_1
从上述日志可以看到,3个不同的线程虽然都在访问同一个ThreadLoal,但是获取的值是不同的。相当于每个线程都有属于自己的一个ThreadLoal副本,各自线程中互不干扰。
ThreadLoal源码分析
ThreadLoal的构造方法是个空方法,直接来看set方法:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
//根据当前线程获取ThreadLoacalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 新建ThreadLocalMap
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//Thread.java
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
注解中指出,该方法是把值赋值给当前线程的局部变量副本。此处代码逻辑比较清晰,如果ThreadLoacalMap不为空则将set操作转移给了ThreadLocalMap,否则创建一个ThreadLoacalMap。ThreadLocalMap是ThreadLoacal的静态内部类,在每个Thread类中都已经定义了初始值为null的ThreadLoacalMap。先来分析下ThreadLocalMap的构造方法:
//初始容量
private static final int INITIAL_CAPACITY = 16;
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
//根据key的hash进行数组坐标的相关计算
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
//设置阈值
setThreshold(INITIAL_CAPACITY);
}
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
从上面代码可以看出,ThreadLoacalMap定义了一个名为table的Entry[]数组,创建一个Entry对象,根据key的相关hash运算得到数组下标,存储在table中。ThreadLocalMap的set方法便是在构造方法的基础上对重复元素进行一些处理:
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//从i开始向后遍历,查看table中是否已经存放过该key的数据或是table中有key为null的
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
//更新value
if (k == key) {
e.value = value;
return;
}
//替换该位置上的Entry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//table中没有空位置且该数据为新增Entry
tab[i] = new Entry(key, value);
int sz = ++size;
//是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
从以上方法可以看出,操作的数据是以ThreadLoacal.ThreadLoacalMap.Entry对象的形式存储,而实际才存储位置是在table数组中。ThreadLoacalMap的set方法和ThreadLoacalMap的的构造方法一样,同样是根据key来计算存放在数组中的下标,便从该下标i开始向后遍历。如果存在该key则进行value的update。如果该下标位置是个空位置(因为Entry采用的是WeakReference弱引用,如果被回收了则不需要再存储改数据)则直接替换成我们新set的数据,而replaceStaleEntry方法里面则执行了一些回收替换操作。如果table中没有空位置且该数据为新增Entry,那么就插入一个新数据,为了防止数组越界,需要对存储容量做计算,判断是否需要扩容和重包装。threshold就是扩容或是重包装的临界点:threshold= len * 2 / 3;
rehash()方法则进行table的重包装和重新设置大小的:
private void rehash() {
//删除一些旧的的Entry
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
上述代码中看到table做了一些删除工作,如果存储量达到了3/4就要去重新计算存储空间。resize方法对table的容量扩充了一倍并重新设定了threshold的值。上面分析了ThreadLocal的set方法,这里分析下它的get方法,如下所示:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
可以发现,ThreadLocal的get方法的逻辑比较清晰,它取出当前线程的ThreadLocalMap.Entry对象,如果这个对象为null那么就返回初始值,初始值由ThreadLocal的setInitialValue方法来描述,默认情况下为null,开发者可以根据需要去重写该方法。而这个获取的ThreadLocalMap.Entry就是set方法存储在table中的数据,直接根据key计算出来下标从table数组中去获取,ThreadLoacal还针对数据有可能丢失的情况做了处理:
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
上述代码逻辑也比较清晰了,ThreadLoacal会对table向后遍历,查找是否有对应的数据,整改table中都没有才会返回null。
从ThreadLocal的set和get方法可以看出,它们所操作的对象都是当前所在线程的ThreadLoacalMap对象中的table数组,所以在不同线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal所做的读写操作都是在各自线程的内部,这就能解释为什么ThreadLocal在多个线程中存储和修改数据都是互不干扰地的了。在Android的消息处理机制中,便用了ThreadLoacal来存储Looper。
从ThreadLoacal的角度来说,ThreadLoacal只是数据存储/获取事件的分发者,在哪个Thread中调用就将数据派发到哪个Thread中,实际存储/获取还是在各种Thread中。而从Thread的角度来说,ThreadLoacal只是存储的key,根据ThreadLoacal来存储和获取相应的value。
推荐阅读
-
Android消息机制三剑客之Handler、Looper、Message源码分析(一)
-
Android消息机制原理,仿写Handler Looper源码解析跨线程通信原理--之仿写模拟Handler(四)
-
Android消息机制之线程间存储ThreadLocal源码分析
-
Android源码分析之Handler消息机制
-
源码分析RocketMQ之CommitLog消息存储机制
-
Android源码分析之Handler消息机制
-
深入理解Handler(二) --- 从源码角度来理解Android线程间消息传递机制
-
Android消息机制之Handler源码分析
-
Android源码分析之消息机制Handler
-
Android源码分析之Handler消息机制