ThreadLocal
大约 4 分钟
简介
ThreadLocal 类提供了一种方式,使得每个线程可以独立地持有自己的变量副本,而不是共享变量。这可以避免线程间的同步问题,因为每个线程只能访问自己的ThreadLocal变量。通过ThreadLocal为线程添加的值只能由这个线程访问到,其他的线程无法访问,因此就避免了多线程之间的同步问题
示例
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
threadLocal.set("A");
System.out.println(Thread.currentThread()+"\t"+threadLocal.get());
},"A").start();
new Thread(()->{
threadLocal.set("B");
System.out.println(Thread.currentThread()+"\t"+threadLocal.get());
},"B").start();
}

原理解析
1. 每个线程里面都自己维护了一个ThreadLocalMap对象
class Thread {
ThreadLocal.ThreadLocalMap threadLocals;
}
ThreadLocal类存在ThradLocalMap的静态内部类,
ThradLocalMap中维护一个Entry对象,Entry继承了WeakReference
class ThreadLocal {
// ThreadLocalMap 是ThradLocal的静态内部类
static class ThreadLocalMap {
// 初始化长度
private static final int INITIAL_CAPACITY = 16;
// Entry数组
private Entry[] table;
// 数组实际大小
private int size = 0;
// ThreadLocak.set()时调用的构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
// 计算key的下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
ThreadLocal、ThreadLocalMap、Entry关系图


2. ThreadLocal.set 源码
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 根据当前线程获取到ThraedLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// map==null,创建ThreadLocalMap
createMap(t, value);
}
// getMap方法是直接返回线程的threadLocals属性
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
// ThreadLocalMap有参构造,创建ThreadLocalMap对象,赋值给线程的threadLocals属性
// 到这一步,ThreadLocalMap创建完成,值也放进了ThreadLocalMap的table数组里
// 这里的key是当前ThreadLocal对象,value是传入的值
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
2.1 ThreadLocalMap.set 源码
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 获取到本次存储Entry的下标
int i = key.threadLocalHashCode & (len-1);
// 这个循环是为了找到是否已存在含有此ThreadLocal的Entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]
) {
ThreadLocal<?> k = e.get();//获取Entry中的ThreadLocal
if (k == key) {
e.value = value; // 更新value值
return;
}
if (k == null) {
// 如果这个Entry的ThreadLocal已经被回收,则替换此下标所在的Entry
replaceStaleEntry(key, value, i);
return;
}
}
// 存储Entry
tab[i] = new Entry(key, value);
int sz = ++size;
// cleanSomeSlots 清理ThradLocal已经被回收的Entry
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 扩容后重新进行hash
rehash();
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
2.2 ThreadLocalMap构造方法
// ThreadLocak.set()时调用的构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
// 计算key的下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 将创建的Entry对象放进数组
table[i] = new Entry(firstKey, firstValue);
// 需改大小为1
size = 1;
// 设置扩容的阈值,当数组大小超过这个阈值将进行扩容
setThreshold(INITIAL_CAPACITY);
}
2.3 ThreadLocalMap.Entry 构造方法
// 构造方法需传入两个参数
// 一个是ThreadLocal对象,一个是value值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
3. ThreadLocal.get 源码
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的threadLocals属性值
ThreadLocalMap map = getMap(t);
if (map != null) {
// map.getEntry(this),是传入的当前ThreadLocal对象,跟set传入时的key一致
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果threadLocals==null,则初始化ThreadLocalMap
return setInitialValue();
}
// 初始化线程的threadLocals属性
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 创建ThreadLocalMap
createMap(t, value);
// 如果还没有调用set方法,那么get方法时就会返回null
return value;
}
protected T initialValue() {
return null;
}
4.ThreadLocal.remove 源码
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
static class ThreadLocalMap {
// 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(); // Reference.clear方法
expungeStaleEntry(i); // 清理Entry数组,如果存在ThreadLocal为空的情况
return;
}
}
}
}
总结
- ThreadLocal内部有一个threadLocals的属性,类型是ThreadLocalMap
- ThreadLocalMap内部维护了一个Entry数组,Entry数组存的是ThreadLocal和Object
- ThreadLocal.set
- 执行getMap方法,getMap方法直接返回线程的threadLocals属性值
- 如果线程的threadLocals属性为null,则创建ThreadLocalMap
- 创建Entry放入到ThreadLocalMap的table数组中
- ThreadLocal.get
- 获取当前线程的ThreadLocalMap
- ThreadLocalMap.getEntry 得到Entry对象
- 返回Entry对象的value值
- ThreadLocal.remove
- 清理当前ThreadLocal对应的Entry
- 会遍历Entry数组,清理已经被回收的ThreadLocal所在的Entry
内存泄漏的问题

key被GC自动收回了,但是value还是留在Map中,而value将永远不会被访问到,造成内存泄露。
解决方案:
- 在ThreadLocal不使用时,调用remove方法,将Entry从Map中移除,即可解决。
- set 方法通过调用 replaceStaleEntry方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏
- get方法会间接调用expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收