Apong's Blog

当你快坚持不住的时候,困难也快坚持不住了

0%

ThreadLocal解析

ThreadLocal —— 线程变量

作用

创建一个在不同线程之间读写相互隔离的变量。

大白话:同一个变量,在不同线程的环境下,访问和修改它的值都互不影响,不同线程允许拥有不同的值。

用法

声明为一个静态的类属性。

以存储 User 信息为例:

提供方法

  • saveUser 设置 User 信息
  • getUser 获取 User 信息
  • removeUser 移除 User 信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

public static void saveUser(UserDTO user){
tl.set(user);
}

public static UserDTO getUser(){
return tl.get();
}

public static void removeUser(){
tl.remove();
}
}

原理

ThreadLocal 对象内部的数据是由 ThreadLocalMap 维护的,

而每个 Thread 又有各自的 ThreadLocalMap。

因此“表面上”线程共享的类属性变量,实际上在每个 Thread 中读写的对象并不是同一个。

假设这有两个线程 A,B 操作一个线程变量 tl。

在 A 线程中 tl 的值是 1。

如果 B 线程想要修改它的值,就需要先找到当前 Thread 的 ThreadLocalMap,然后修改 map 中 tl 这个 key 所匹配的 value。

什么叫做维护呢?

这里以上述的 tl 表示 ThreadLocal 实例,并贴出该类的源码。

set

当 tl 进行 set 操作时,以自身实例作为 key,存储对象作为 value,存入了 ThreadLocalMap 中。

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

get

同理进行 get 操作时,以自身实例作为 key,索引 ThreadLocalMap 中的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}

remove

remove 操作也是如此,以自身实例为 key,移除这一对键值对。

1
2
3
4
5
6
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}

本质

并不是 ThreadLocal 为每个线程提供了不同的变量副本,而是其借助不同线程中的 ThreadLocalMap 存储了当前线程的变量副本。

内存泄漏

内存泄漏:指程序中已分配的内存未能成功释放,导致可用内存逐渐减少的现象。

为什么线程变量通常声明为一个静态类属性,而不是局部变量呢?

因为 垃圾回收 机制。

如果将 ThreadLocal 实例声明为一个局部变量,在作用域结束时,会回收掉这个变量引用,导致 ThreadLocalMap 中存储了一个 key 为 null 的键值对,无法进行访问,也不能主动销毁。

从而发生内存泄漏。

疑问:question:

虽然引用被回收了,但是内存空间不会被释放吧,key 不至于为 null 吧?因为 map 的 key 保存了这个实例?

答:

Map 的 keys 保留了这个实例对象,所以这个 key 并不是 null;

只不过丢失了对这个 key 的引用,无法主动的查询对应的值,但是可以通过 map 的 keys,一一返回 value。

所以上述解释 “导致 ThreadLocalMap 中存储了一个 key 为 null 的键值对” 改为:“导致 ThreadLocalMap 中存储了一个“消失的” key 的键值对”

上述解释仍存疑:question:

弱引用?和垃圾回收的关系?