为什么 ThreadLocal 可以做到线程隔离?

开发 前端
原来 ThreadLocal 就是把我们要传递的对象放到了当前线程的 threadLocals 属性中。也就是说每个线程在用 ThreadLocal 保存对象时,其实就是将对象放到了当前线程实例对象的 threadLocals 属性里面。

​对于 ThreadLocal 我们都不陌生,它的作用如同它的名字——用于存放「线程本地」变量。

先通过一个小例子感受一下:

private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

public static void main(String[] args) throws Throwable {

Thread threadOne = new Thread(()->{
threadLocal.set("ThreadOne:" + Thread.currentThread().getName());
log.info("线程 One 本地变量值为:{}", threadLocal.get());
threadLocal.remove();
log.info("线程 One remove 后本地变量值为:{}", threadLocal.get());
});

Thread threadTwo = new Thread(()->{
threadLocal.set("ThreadTwo:" + Thread.currentThread().getName());
log.info("线程 Two 本地变量值为:{}", threadLocal.get());
});

threadOne.start();
threadTwo.start();
}

运行结果:

线程 One 本地变量值为:ThreadOne:Thread-0
线程 One remove 后本地变量值为:null
线程 Two 本地变量值为:ThreadTwo:Thread-1

OK,从效果上看,ThreadLocal 确实是线程隔离的,那么,它是如何做到线程隔离的呢?下面我们扒一扒源码,看看它是如何做到的:

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

set() 方法的逻辑如下:

  • 获取当前线程
  • 根据当前线程获取一个 ThreadLocalMap 对象
  • 如果 map 不为 null 则保存
  • 如果 map 为 null 则创建一个 map

getMap() 和 createMap() 方法都干了啥呢?我们点进去看:

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

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

进入到两个方法内部后发现,不管执行哪个分支,最终是把值保存到了当前线程的 threadLocals 属性中。

查看 Thread 类的源码,你会发现类中定义了一个 threadLocals 属性,且初始值为 null,其类型为ThreadLocal.ThreadLocalMap。

public class Thread implements Runnable {
// ...
ThreadLocal.ThreadLocalMap threadLocals = null;
// ...
}

到此,我们发现了,原来 ThreadLocal 就是把我们要传递的对象放到了当前线程的 threadLocals 属性中。也就是说每个线程在用 ThreadLocal 保存对象时,其实就是将对象放到了当前线程实例对象的 threadLocals 属性里面。这样一来线程之间自然就是互相独立的啦。

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

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);
return value;
}

ThreadLocal 的 get() 方法其实和 set() 方法逻辑很相似,先从当前线程的 threadLocals 属性中取,如果该属性为 null,那么就初始化。

当线程结束时,会调用当前线程实例的 exit() 方法,将 threadLocals 设置为 null,以便垃圾回收器将其回收掉。

// THread 类中的方法
private void exit() {
// ...
threadLocals = null;
// ...
}

最后,有一点需要格外注意:用完 ThreadLocal 一定要记得手动调用 remove() 方法,否则可能会产生脏数据甚至产生内存泄漏。

为啥呢?上面不是说线程结束时,会将 threadLocals 置为 null 吗?

是的,线程结束时,确实会做清理工作。

但,如果线程一直不结束呢?如果线程会被复用呢?比如使用了线程池。

所以,使用 ThreadLocal 一定要手动 remove()。​

责任编辑:武晓燕 来源: 今日头条
相关推荐

2017-02-09 11:54:49

联想企业网盘

2021-08-10 09:58:59

ThreadLocal内存泄漏

2022-10-18 08:38:16

内存泄漏线程

2014-09-12 10:23:16

技术程序员

2014-09-11 10:42:41

程序员

2018-04-26 13:41:59

IT

2021-03-28 23:37:35

线程专属变量

2016-11-01 08:45:31

2023-03-21 08:02:36

Redis6.0IO多线程

2023-12-20 14:35:37

Java虚拟线程

2024-01-19 08:42:45

Java线程字符串

2020-04-22 20:35:02

HashMap线程安全

2020-10-23 14:58:07

戴尔

2024-03-13 07:53:57

弱引用线程工具

2021-12-28 09:50:18

Redis单线程高并发

2020-02-11 11:37:37

比特币区块链加密

2020-09-11 06:39:29

ThreadLocal线程

2020-04-29 14:10:44

Java线程池编程语言

2022-06-09 16:48:10

TensorFlow机器学习

2015-11-30 11:17:27

印度人硅谷
点赞
收藏

51CTO技术栈公众号