Threadlocal 源码阅读

ThreadLocal 是什么

threadlocal 可为每个线程存储一份单独的变量,threadlocal 里面的值 线程之间不共享

使用方式

1
2
3
4
5
6
7
private static ThreadLocal<String> local = new ThreadLocal<>();

local.set("1")

String localValue = local.get()

local.remove()

ThreadLocal 如何保证线程间不可见

ThreadLocal.set

1
2
3
4
5
6
7
8
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); //将 this(指向 ThreadLocal) 和 value 组对 放进去
else
createMap(t, value); //则初始化一个 跟 t(当前线程) 相关的 ThreadLocalMap
}

先看 createMap

1
2
3
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

可见 Thread 内部有个 ThreadLocalMap 类型的属性 t.threadLocals,并且实例化 一个 ThreadLocal 和 value 组对 的 ThreadLocalMap
接着看 ThreadLocalMap 的构造方法

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

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

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

ThreadLocalMap 初始化一个 Entry 的数组,并算了 ThreadLocal 的一个 hash 值作为 table 的数组索引,将 Entry 对象放到数组的指定位置上

Entry 为 WeakReference 类型,可以保证当Entry 里的 value 对象被除WeakReference对象之外所有的对象解除引用后,该 value 对象便可以被GC回收

接着 回去看 map.set(this, value);

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
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);

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();
}

拿着 ThreadLocal 类型的 key的 hashcode 去 Entry 数组里面找索引,拿到 value(tab[i]), 如果索引值存在就拿新 value 换旧值

for 循环里面的 if (k == key) 之后的代码,应该就是 注释里面解释的,对一个 新的 ThreadLocal 的处理逻辑

1
2
3
4
// 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.

ThreadLocal.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) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

逻辑就是拿到 当前 Thread 的 threadLocalMap,通过 map.getEntry(this) 拿到 与 threadlocal 成对的 value值

ThreadLocal.remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//ThreadLocal.remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

//ThreadLocalMap.remove
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}

获取当前 Thread 的 threadlocalMap 并对其内部的 Entry 数组 遍历置为 null,便于被 GC 掉

总结

  • ThreadLocal 通过操作 Thread 内部的 ThreadLocalMap 来达到线程之间隔离

  • ThreadLocal 跟线程绑定,线程执行完,ThreadLocal 会被GC 掉,由于线程池中的线程生命周期长,所以 使用 ThreadLocal 要注意 TreadLocal.remove 操作