ThreadLocal原理

前言

ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

每个线程中都保有一个ThreadLocalMap的成员变量,ThreadLocalMap内部采用WeakReference数组保存,数组的key即为ThreadLocal内部的Hash值。

ThreadLocal的接口方法

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
// 设置当前线程的线程局部变量的值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 返回当前线程所对应的线程局部变量
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();
}
// 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK5.0新增的方法,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用方法清除方法的局部变量并不是必须的操作,但它可以加快内存回收的速度
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
// 返回该线程局部变量的初始值,该方法是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第一次调用get或set方法时候才执行,并且只执行一次,ThreadLocal中的缺省实现直接返回一个null
protected T initialValue() {
return null;
}

ThreadLocal实现原理

基本思路

在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

源码实现

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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);
}
protected T initialValue() {
return null;
}
public ThreadLocal() {
}
public T get() {
Thread t = Thread.currentThread(); // 取得当前线程
ThreadLocalMap map = getMap(t); // 通过getMap方法获取到一个map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // this是指当前的ThreadLocal对象
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 在getMap方法中,调用当前线程t,返回其成员变量threadLocals
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
......
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold;
}
}

首先看到ThreadLocal的get()方法,这个方法先取得当前线程,然后通过getMap()方法返回这个线程的成员变量threadLocals,threadLocals是一个ThreadLocalMap类,即ThreadLocal类的一个内部类,可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

然后继续看ThreadLocal的setInitialValue()方法的具体实现,可以看到,如果map不为空,就设置键值对,为空,再创建map,接下来看到createMap()方法的实现,可知,这个方法里面正好是创建本线程里面的threadLocals的。

threadLocal和线程同步机制的比较

threadLocal和其他所有的同步机制都是为了解决多线程中的对同一变量的访问冲突。

在普通的同步机制中,是通过对象加锁来实现多个线程对统一变量的安全访问的,这时候变量是多个线程共享的,使用这种机制需要很细致的分析在什么时候对变量进行读写、什么时候需要锁定某个对象,什么时候释放该对象的锁等等。同步机制中一般使用synchronized关键字来保证同一时刻只有一个线程对共享变量进行操作。但有些情况下,synchronized不能保证多线程对共享变量的正确读写,例如类有一个类变量,该类变量会被多个类方法读写,当多线程操作该类的实例对象时候,如果线程对类变量有读写、写入操作就会发生类变量读写错误,即便是在类方法前加上synchronized也无效,因为同一个线程在两次调用方法之间时锁是释放的,这时其他线程可以访问对象的类方法,读取或修改类变量。这种情况下可以将类变量放到ThreadLocal类型的·变量中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。

ThreadLocal从另一个角度来解决多线程的并发访问,threadLocal为每一个线程维护一个和该线程绑定的变量的副本,使每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。threadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的整个变量封装进threadLocal。

ThreadLocal并不能替代同步机制,两者面向的问题领域不同。

同步机制我是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式。而threadLocal是隔离多个线程的数据共享,从根本就不在多个线程之间共享变量,这样当然不需要对多个线程进行同步了。

所以,如果需要进行多个线程之间进行通信,则使用同步机制,如果需要隔离多个线程之间的共享冲突,可以使用threadLocal。这将暨大1的简化程序,是程序更加易读、简洁。

对于多线程资源共享的问题,同步机制采用了“以时间换空间”,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

ThreadLocal是解决线程安全问题的一个很好的思路,它通过为每个线程都提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更加简单,更方便,且结果程序拥有更高的并发性。

总结

  • 原理

    首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来储存实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对 ThreadLocal类中的threadLocals进行初始化,并且以当前的ThreadLocal变量为键值,以ThreadLocal要保存的变量副本为value,存到threadLocals。

  • 应用场景

    ThreadLocal的大致使用场景有以下两个:

    ① 当某些数据以线程为作用域,并且不同线程拥有不同数据副本的时候

    ② 复杂逻辑下对象传递,比如监听器的传递

    最常见的ThreadLocal使用场景用来解决数据库连接、Session管理等。

    在android消息机制源码中有一个典型的应用场景,主要用来存取不同线程中的Looper对象。

    在android开源框架EventBus中也有使用ThreadLocal的场景。