多线程-ThreadLocal

前言

ThreadLocal的特性和底层实现。

用法

1
2
3
4
5
6
7
8
/* 声明ThreadLocal */
ThreadLocal<Obj> tl = new ThreadLocal<>();
/* 设置ThreadLocal */
tl.set(obj);
/* 读取ThreadLocal */
Obj obj2 = tl.get();
/* 使用完后,需要手动remove,否则会导致内存泄露 */
tl.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);
else
createMap(t, value);
}

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

Remove

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

上面三个函数一开头都用到了getMap()方法,我们来看一下:

1
2
3
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

非常简单,直接返回线程Thread对象中的threadLocals变量,我们来看一下Thread类:

1
2
3
4
5
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}

可以看到Thread类中确实有 ThreadLocalMap 实例,也就是说,每个线程中都有这么一张Map,再结合 SetGet 方法,可以看出,这个Map中,各个条目以ThreadLocal实例为key,值为value来存储。

同一个ThreadLocal实例对不同Thread隔离,就是这么实现的。

此外值得注意的是,ThreadLocalMap中的key是弱引用,之所以是弱引用是因为:ThreadLocalMap是Thread类的成员,因此其生命周期是和Thread相同的,如果该map中的key是ThreadLocal实例,如果某个ThreadLocal实例用完不用了,但由于map中还有对该实例的引用,因此,GC不会回收该对象,就会导致内存泄露,而使用弱引用之后,当外部的强引用消失之后,只剩弱引用,就会被回收了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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; // Default to 0
...
}

ThreadLocalMap中的value还是强引用,因此如果ThreadLocal不用了,还是要调用remove()方法。

使用场景

比如连接池应用,使用ThreadLocal可以保存连接。