Welcome to Ray's Blog

Stay Hungry Stay Foolish - Steve Jobs

0%

Android ThreadLocal 知识笔记


定义

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

使用场景

一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal
另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递。

原理

简单原理概述

不同线程访问同一个ThreadLocalget方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThradLocal的索引去查找出对应的 value 值。很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的的线程中维护一套数据的副本并且彼此互不干扰。

内部实现

ThreadLocal是一个泛型类,它的定义为:public class ThreadLocal<T>,只要弄清楚ThreadLocalgetset方法就可以明白它的工作原理;

set(T value)方法

1
2
3
4
5
6
7
8
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

在上面set方法中,首先会通过 values 方法来获取当前线程的ThreadLocal数据。获取方式很简单:在Thread类的内部有一个成员专门用于存储线程的ThreadLocal的数据:ThreadLocal.Values localValues,因此获取当前线程的ThreadLocal数据就变得异常简单了。如果localValues的值为 null,那么久需要对其进行初始化,初始化后再将ThreadLocal的值进行存储。在localValues内部有一个数组:private Object[] table,ThreadLocal的值就存在在这个table数组中。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 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数组中的存储位置总是为ThradLocalreference字段所标识的对象的下一个位置,比如ThreadLocalreference对象在table数组中的索引为index,那么ThreadLocal的值在table数组中的索引就是index+1。最终ThreadLocal的值将会被存储在table数组中:table[index+1]=value

get()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}

get()方法的逻辑:取出当前线程的localValues对象,如果这个对象为 null 那么就返回初始值,初始值由ThreadLocalinitialValue方法来描述,默认情况下为 null,可以重写该方法,返回自定义的值。如果localValues对象不为 null,那么就取出它的table数组并找出ThreadLocalreference对象在table数组中的位置,然后table数组中的下一个位置所存储的数据就是ThreadLocal的值。

总结

ThreadLocalsetget方法可以看出,它们所操作的对象都是当前线程的localVaules对象的table数组,因此在不同线程中访问同一个ThreadLocalsetget方法,它们对ThreadLocal所做的读/写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据。