Dawn's Blogs

分享技术 记录成长

0%

Java面试之并发编程 (4) ThreadLocal

简介

ThreadLocal 使得每一个线程有自己专属的本地变量。如果创建了一个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。可以使用 get()set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

原理

ThreadLocalMap

从 Thread 类的源码入手,Thread 类中有一个 threadLocals 和 inheritableThreadLocals 变量,二者都是 ThreadLocalMap 类型的变量。默认情况下这两个变量都是 null,只有当前线程调用 ThreadLocal 类的 setget方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()set()方法。

1
2
3
4
5
6
7
8
9
public class Thread implements Runnable {
//......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;

//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//......
}

ThreadLocal 原理

从 ThreadLocal 类的 set 方法可以看出,通过 Thread.currentThread 获取当前线程对象,再通过 getMap 获取到当前线程对象的 ThreadLocalMap 对象。

所以,最终变量是放在当前线程的 ThreadLocalMap 中,并不是存储在 ThreadLocal 对象中,ThreadLocal 仅仅是 ThreadLocalMap 的封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void set(T value) {
//获取当前请求的线程
Thread t = Thread.currentThread();
//取出 Thread 类内部的 threadLocals 变量(哈希表结构)
ThreadLocalMap map = getMap(t);
if (map != null)
// 将需要存储的值放入到这个哈希表中
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

每一个线程对象都有一个 ThreadLocalMap,可以存储以 ThreadLocal 为 key,Object 对象为 value 的键值对。

ThreadLocal 数据结构

内存泄漏问题

ThreadLocalMap 中的 key 为弱引用,而 value 为强引用。如果 ThreadLocalMap 在没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

这样以来,ThreadLocalMap 就会出现 key 为 null 的 Entry,如果我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。不过 ThreadLocalMap 已经考虑了这种情况,在调用 get、set、remove 方法时,会清理掉 key 为 null 的记录。