Fork me on GitHub

Java并发编程实战———ThreadLocal深度解析

ThreadLocal提供了线程的局部变量,每个线程都可以通过set()get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离

如何使用ThreadLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

public void start() {
for(int i=0; i<10; i++) {
new Thread(new Runnable() {
@override
public void run() {
threadLocal.set(i);
threadLocal.get();
threadLocal.remove();
}
}).start();
}
}
}

首先需要创建一个线程共享的ThreadLocal对象,该对象用于存储Integer类型的值,然后在每条线程中调用以下方法操作ThreadLocal:

  • set(obj):向当前线程中存储数据
  • get():获取当前线程中的数据
  • remove():删除当前线程中的数据

ThreadLocal实现原理

ThreadLocal

ThreadLocal并不维护ThreadLocalMap,它只是相当于一个工具包,提供了操作该容器的方法,如getsetremove等。而ThreadLocal内部类ThreadLocalMap才是存储数据的容器,并且该容器由Thread维护。

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值。

ThreadLocalMap由一个个Entry对象构成,Entry的代码如下:

1
2
3
4
5
6
7
8
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;

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

Entry继承自WeakReference<ThrealLocal<?>>,一个EntryThreadLocal对象和Object构成。由此可见,EntrykeyThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。

ThreadLocalsetget方法:

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

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

当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。

get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value

由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

ThreadLocal的内存泄露问题

ThreadLocalMap中,只有key是弱引用,value仍然是一个强引用。当某一条线程中的ThreadLocal使用完毕,没有强引用指向它的时候,这个key指向的对象就会被垃圾收集器回收,从而这个key就变成了null;然而,此时valuevalue指向的对象之间仍然是强引用关系,只要这种关系不解除,value指向的对象永远不会被垃圾收集器回收,从而导致内存泄漏!

不过不用担心,ThreadLocal提供了这个问题的解决方案。

每次操作setgetremove操作时,ThreadLocal都会将keynullEntry删除,从而避免内存泄漏。

那么问题又来了,如果一个线程运行周期较长,而且将一个大对象放入ThreadLoalMap后便不再调用setgetremove方法,此时该仍然可能会导致内存泄漏。

这个问题确实存在,没办法通过ThreadLocal解决,而是需要程序员在完成ThreadLocal的使用后要养成手动调用remove的习惯,从而避免内存泄漏。

求鼓励,求支持!