ThreadLocal

概述

ThreadLocal类用来提供线程内部的内部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型,用于关联线程和线程的上下文

作用:

提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数和组件之间一些公共变量的复杂度

常用方法

几个ThreadLocal常用的方法

方法声明 描述
ThreadLocal() 创建ThreadLocal对象
public void set(T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove() 移除当前线程绑定的局部变量

ThreadLocal和Synchronized的区别

都能处理多线程并发访问变量的问题,但是处理角度和思路不同

Synchronized ThreadLocal
原理 同步机制采用“以时间换空间的方式”,只提供了一份变量,让不同的线程排队访问 ThreadLocal采用“以空间换时间的”的方式,为每个线程提供了一份变量的副本,从而实现同时访问而像互不干扰
侧重点 多线程之间访问资源的同步 多线程中让每个线程之间的数据相互隔离

内部结构

常见误解

如果我们不去看源代码的话,可能会猜测ThreadLocal的设计是:每个ThreadLocal都创建一个Map,然后线程作为Map的key,存储在局部变量作为Map的value,这样就能达到各个线程的局部变量隔离的效果。这就是简单的设计方法,JDK最早期的ThreadLocal确实是这样设计的

现在的设计

在JDK8中ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object

具体过程:

  1. 每个线程内部都要有一个Map(ThreadLocalMap)
  2. Map里面存储ThreadLoacal对象(key)和线程的变量副本(value)
  3. Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向Map获取和设置的变量值
  4. 对于不同的线程,每次获取副本值时,别的线程并不能获取当前线程的副本值,形成副本隔离,互不干扰

核心源码

set方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}

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

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

get方法

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

private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}

remove方法

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

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

源码分析

ThreadLocal的操作实际上是围绕着ThreadLocalMap展开的

基本结构

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,独立的方式实现了Map的功能,其内部的Entry也是独立实现的

1
2
3
4
5
6
7
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);
}

在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。不过Entry中的key只能是ThreadLocal对象,这点构造方法中已经限定死了

另外,Entry继承WeakReference,也就是key(ThreadLocal)是弱引用,其目的是将ThreadLocal对象的生命周期和线程周期绑定

弱引用与内存泄露

有些人在使用ThreadLocal的过程中会发现有内存泄漏的情况发生,就猜测这个内存泄漏跟Entry中使用的弱引用的key有关系,这种理解是错误的