Don’t forget, a person’s greatest emotional need is to feel appreciated.
莫忘记,人类情感上最大的需要是感恩。

在阅读Handler源码时发现了这么一个东西,本想直混在其他博客中一笔带过,但仔细想了下这个东西还是蛮重要的,于是开了这篇博客。

ThreadLocal

threadlocal使用方法很简单

1
2
3
static final ThreadLocal<T> sThreadLocal = new ThreadLocal<T>();
sThreadLocal.set()
sThreadLocal.get()

threadlocal而是一个线程内部的存储类,可以在线程内存储数据,数据存储以后,只有存储的线程可以取到被存储数据,其他线程获取则为缺省值 ,官方解释如下。

1
2
3
4
5
6
7
8
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/

大致意思就是ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

这一切的关键点都是Thread内的变量threadLocals,类型为ThreadLocalMap。

作为一个存储数据的类,关键点就在get和set方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//set 方法
public void set(T value) {
//获取调用set方法时所在线程
Thread t = Thread.currentThread();
//获取线程成员threadLocals,即ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果存在map就直接set,没有则创建map并set
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

//getMap方法
ThreadLocalMap getMap(Thread t) {
//thred中维护了一个ThreadLocalMap
return t.threadLocals;
}

//createMap
void createMap(Thread t, T firstValue) {
//实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

从上面代码可以看出每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。

假设有ThreadLocal sample= new ThreadLocal();
从上面代码不难看出在不同线程访问sample的get/set方法时,操作的是对应线程的ThreadLocalMap,是不同的实例对象。
这就是数据隔离的原因。

Thread

1
2
3
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

Thread中关于ThreadLocalMap部分的相关声明,接下来看一下createMap方法中的实例化过程。

ThreadLocalMap

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用
//Entry是key-value形式的封装,key为ThreadLocal,value为ThreadLocal的值
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

//ThreadLocalMap构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//内部成员数组,INITIAL_CAPACITY值为16的常量
table = new Entry[INITIAL_CAPACITY];
//位运算,结果与取模相同,计算出需要存放的位置
//通过key,计算出hash值,把这个值作为数组中的position
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

//ThreadLocalMap中set方法。
private void set(ThreadLocal<?> key, Object value) {

// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.

Entry[] tab = table;
int len = tab.length;
//获取索引值,这个地方是比较特别的地方
int i = key.threadLocalHashCode & (len-1);

//遍历tab如果已经存在则更新值
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();

if (k == key) {
e.value = value;
return;
}

if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}

//如果上面没有遍历成功则创建新值
tab[i] = new Entry(key, value);
int sz = ++size;
//满足条件数组扩容x2
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

在ThreadLocalMap中的set方法与构造方法能看到以下代码片段。

  • int i = key.threadLocalHashCode & (len-1)
  • int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)
    简而言之就是将threadLocalHashCode进行一个位运算(取模)得到索引i,

不同的ThreadLocal对象是存储在tab数组中的不同position,position的计算和threadLocalHashCode有关。
threadLocalHashCode对于实例化后的ThreadLocal是固定且唯一的,也就是说每个ThreadLocal实例对应的position是固定唯一的。

到这里能发现如下两点:

  1. 对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程里set/get时操作的是每个线程自己的数组Entry[] 。
  2. 对于同一线程的不同ThreadLocal来讲,这些ThreadLocal存储在同一个数组Entry[],只是他们存储的position不同。

threadLocalHashCode代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadLocal<T> {
//成员变量
private final int threadLocalHashCode = nextHashCode();

//静态变量
private static AtomicInteger nextHashCode =
new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

//静态方法
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}

每次实例化ThreadLocal,就会初始化threadLocalHashCode,从而调用静态方法nextHashCode,达到hash值递增的目的,增量为0x61c88647。

0x61c88647是斐波那契散列乘数,它的优点是通过它散列(hash)出来的结果分布会比较均匀,可以很大程度上避免hash冲突,已初始容量16为例,hash并与15位运算计算数组下标结果如下:

hashCode 数组下标
0x61c88647 7
0xc3910c8e 14
0x255992d5 5
0x8722191c 12
0xe8ea9f63 3
0x4ab325aa 10
0xac7babf1 1
0xe443238 8
0x700cb87f 15
get()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//ThreadLocal中get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

//ThreadLocalMap中getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}

理解了set方法,get方法也就清楚明了,无非是通过计算出索引直接从数组对应位置读取即可。

ThreadLocal实现主要涉及Thread,ThreadLocal,ThreadLocalMap这三个类。关于ThreadLocal的实现流程正如上面写的那样,实际代码还有许多细节处理的部分并没有在这里写出来。

ThreadLocal特性

ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

正因为ThreadLocal的线程隔离特性,使他的应用场景相对来说更为特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。