java.lang.ThreadLocal
ThreadLocal 是一个创建线程局部变量的类。
通常创建的共享变量是可以被任一个线程访问并修改的,存在线程安全问题。使用 ThreadLocal 创建的变量只有当前线程才可访问,其他线程无法修改和访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
| public class DateUtils {
public static final ThreadLocal<DateFormat> df = new ThreadLocal<>() {
// 重写 initialValue 可设置初始值
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
}
}
// ---
DateUtils.df.get().format(new Date());
|
原理
每一个线程的 Thread 对象中都有一个 ThreadLocal.ThreadLocalMap
对象,这个对象存储了一组以 ThreadLocal.threadLocalHashCode
为键,以本地线程变量为值的 Key-Value
对。
ThreadLocal
对象就是当前线程的 ThreadLocalMap
的访问入口,每一个 ThreadLocal
对象都包含了一个独一无二的 threadLocalHashCode
值,使用这个值,就可以在线程 K-V
值对中找到对应的本地线程变量。
1
2
3
4
5
| public class Thread implements Runnable {
// ...
ThreadLocal.ThreadLocalMap threadLocals = null;
// ...
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public class ThreadLocal<T> {
// ...
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Enrty(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Enrty[] table;
}
// ...
}
|
set()
- 获取当前线程
- 利用当前线程作为句柄,获取一个 ThreadLocalMap 对象
- 如果 ThreadLocalMap 对象不为空,则设置值,否则创建这个 ThreadLocalMap 对象并设置值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| 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()
- 获取当前线程,并获取当前线程的 ThreadLocalMap 对象
- 如果当前线程的 ThreadLocalMap 已经初始化,返回当前 ThreadLocal 为 Key 的值
- 如果 ThreadLocalMap 没有被初始化,或者不存在以 ThreadLocal 为 Key 的值,将创建一个新的 Value,并返回
- initialValue 默认为 null,可进行重写
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
29
30
31
32
33
34
35
36
| // ThreadLocal#get
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();
}
// ThreadLocal#setInitialValue
private T setInitialValue() {
T value = initialValue(); // 获得一个初始值,未重写默认为 null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
// ThreadLocal#initialValue
protected T initialValue() {
return null; // 默认为 null,可被重写
}
// ThreadLocalMap#createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
|
remove()
- 通过调用 ThreadLocalMap 的 remove() 方法完成移除操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
// ---
// 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();
expungeStaleEntry(i);
return;
}
}
}
|
内存泄露
指程序申请内存后,一直无法释放已经申请的内存。
ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 不存在外部的强引用时,下次 GC 时,该 key 就会被回收,但是 value 还存在着强引用,如果没有手动地 remove() 或者线程一直没有结束(例如使用线程池),value 就会一直存在,导致内存泄漏。
解决办法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| private void rehash() {
// 进行全量清理
expungeStaleEntries();
if (size >= threshold - threshold / 4)
resize();
}
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
|
应用场景
线程资源持有,代替参数显示传递
全局存储信息,在需要的地方直接获取
解决线程安全问题,达到线程隔离
参考