Java ThreadLocal

ThreadLocal 是线程局部变量,每个线程都有独立的一个变量。通过创建对象副本,达到线程安全的目的。
它并不是用来解决在多线程并发环境下资源共享问题的,而是用来提供线程内的局部变量,这样每个线程都自己管理自己的局部变量,别的线程操作的数据不会对当前线程产生影响。

示例:

1
2
3
4
5
6
7
8
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};

SimpleDateFormat dateFormat = dateFormatThreadLocal.get();

同一个线程获取相同的实例,不同的线程获取不同的实例。

类结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* This class provides thread-local variables.
*/
public class ThreadLocal<T> {
private Entry[] table;

public ThreadLocal() {
}

static class ThreadLocalMap {

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

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

主要方法

initialValue() 初始化

懒加载,在 get() 方法中调用。

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

protected T initialValue() {
return null;
}

该方法每个线程最多调用一次。如果调用了 remove() 方法,再次调用 get() 时会再次初始化。

set() 设置局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

void createMap(Thread t, T firstValue) {
// 为当前线程的 threadLocals 变量赋值
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

get() 获取局部变量的

1
2
3
4
5
6
7
8
9
10
11
12
13
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
// 如果没有设置过局部变量,获取初始化变量
return setInitialValue();
}

如果调用 get 之前没 set 过,则 get 内部会执行 initialValue 方法进行初始化。

remove() 移除局部变量

1
2
3
4
5
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

为了防止内存泄露,使用该方法把 ThreadLocalMap 从当前线程移除。

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
static class ThreadLocalMap {
// 弱引用,防止 Key 出现内存泄漏
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

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

// 初始容量为 16
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold;

// 阈值为长度的 2/3
private void setThreshold(int len) {
threshold = len * 2 / 3;
}

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 获取在数组中的 index
int i = key.threadLocalHashCode & (len-1);

// 如果当前位置不为空,出现了 hash 冲突,从当前 index 往后遍历
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;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
}

FAQ

  1. ThreadLocal 和 Synchronized 的区别?
    同步机制保证的是共享变量;而 ThreadLocal 线程私有变量。

  2. ThreadLocal 存储在 jvm 的哪个区域?
    ThreadLocal 对象也是对象,对象就在堆。

  3. 真的只是当前线程可见吗?
    不是。可以通过 Thread#inheritableThreadLocals 变量实现,它也是 ThreadLocal.ThreadLocalMap 类型。

  4. ThreadLocal 里的对象一定是线程安全的吗?
    如果 ThreadLocal 保存的是共享变量,也会出现线程安全问题。

  5. 会导致内存泄漏么?
    Entry 对 key 是弱引用,不会导致内存泄漏,但是对 value 是强引用,可能导致内存泄漏。一般的线程终止以后,由于引用链断裂,ThreadLocalMap 中的数据也会被回收。
    如果是线程池中的核心线程需要显示调用 remove() 方法移除局部变量。
    java ThreadLocal 引用图

    其中虚线表示弱引用,从该图可以看出,一个 Thread 维持着一个 ThreadLocalMap 对象,而该 Map 对象的 key 又由提供该 value 的 ThreadLocal 对象弱引用提供,所以这就有这种情况:如果 ThreadLocal 不设为 static 的,由于 Thread 的生命周期不可预知,这就导致了当系统 gc 时将会回收它,而 ThreadLocal 对象被回收了,此时它对应 key 必定为 null,这就导致了该 key 对应得 value 拿不出来了,而 value 之前被 Thread 所引用,所以就存在 key 为 null、value 存在强引用导致这个 Entry 回收不了,从而导致内存泄露。
    所以避免内存泄露的方法,是对于 ThreadLocal 要设为 static 静态的,除了这个,还必须在线程不使用它的值时手动 remove 掉该 ThreadLocal 的值,这样 Entry 就能够在系统 gc 的时候正常回收,而关于 ThreadLocalMap 的回收,会在当前 Thread 销毁之后进行回收。

总结

Thread 维护了一个 ThreadLocal.ThreadLocalMap 类型的变量,ThreadLocalMap 里维护了一个 Entry[] 数组,Entry 保存的是以 ThreadLocal 为 key,传入的值为 value 的键值对。