目录
一、ThreadLocal 概述
1.1 ThreadLocal 是什么
1.2、ThreadLocal 的作用
二、ThreadLocal 的使用和源码分析
2.1 ThreadLocal 如何使用
2.1.1 常见方法
2.1.2 使用示例
2.2 ThreadLocal 源码分析
2.2.1 set() 方法分析,以及存储结构
2.2.2 ThreadLocalMap 分析
2.3 ThreadLocal 的内存泄漏
2.3.1 强引用和弱引用
2.3.2 ThreadLocal 机制中可能存在的内存泄漏情况
2.3.3 若使用线程池时,没有清理 ThreadLocal
ThreadLocal 会为每一个线程中都创建一个副本,每个线程仅可访问自己内部的副本变量,无法互相访问其他线程的副本变量。
本质上是每个线程都有一个 ThreadLocalMap 类型的变量 threadLocals,以设置的 ThreadLocal 对象为 Key,要保存的值为 Value,存储到这个 threadLocals 中。
由于每个线程都有自己的 threadLocals,故实现了线程隔离。且 map 中可以记录多个值,可以通过 new 新的 ThreadLocal 对象实现多个值保存。
ThreadLocal 一般用在线程安全问题中,通过为每一个线程创建变量副本来解决共享变量并发访问的冲突问题。如线程池,或是会话管理。
● set() 保存值到 ThreadLocal 中
● get() 从 ThreadLocal 中取值
● remove() 从 ThreadLocal 中取值
public class TestThLocal {
static ThreadLocal threadLocal = new ThreadLocal<>();
static ThreadLocal threadLocal2 = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
threadLocal2.set(new Person());
threadLocal.set(new Person());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("删除前获取: " + threadLocal.get());
System.out.println("删除前获取2: " + threadLocal2.get());
threadLocal.remove();
threadLocal2.remove();
System.out.println("删除后获取: " + threadLocal.get());
System.out.println("删除后获取2: " + threadLocal2.get());
}).start();
}
static class Person {
String name = "xiaozhang";
}
}
先查看 set() 方法的源码
public void set(T value) {
// 获取到当前线程
Thread t = Thread.currentThread();
// 从当前线程中取出这个线程的变量 threadLocals
ThreadLocalMap map = getMap(t);
if (map != null)
// this 是调用 set() 方法的 ThreadLocal 对象
// value 是要保存的值
map.set(this, value);
else
// map 不存在,则新建一个 map
createMap(t, value);
}
那么,看一下 getMap() 的作用,可以看到是获取当前这个线程的变量 threadLocals,这个变量的类型是 ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
由此我们可以总结:
ThreadLocal 的原理就是在每一个线程中都有一个 ThreadLocalMap 类型的变量 threadLocals,用于存储ThreadLocal 设置的值。存储方式就是,把 ThreadLocal 对象作为 Key 值,要存储的内容作为 Value,存储有两个特点:支持多个 ThreadLocal 存储,相同的 ThreadLocal 会被覆盖
结构图如下:
由于每个线程都是维护自己的 threadLocals 变量,故可以实现线程隔离
可以看到存储值是设置 ThreadLocalMap 的键值对来实现的,
进而查看 Entry 的结构,发现 Entry 是一个弱引用。这就引出了 ThreadLocal 的内存泄漏问题
从 2.2.2 中我们了解到,用来存储键值对的 Entry 是一个弱引用对象。
● 强引用
对象只要有引用,就不会被回收
● 弱引用
当没有强引用来引用这个对象,则GC回收器到来时,就会回收掉这个弱引用对象
● 线程关闭了,但 ThreadLocal 没有被回收造成内存泄漏
在 Thread 源码中,关闭线程会直接将 threadlocals 设为 null,
这是若 ThreadLocal 是强引用,则只要线程不退出,内部存储的 ThreadLocal 也不会被回收,就变成了内存泄漏。
故 jdk 中使用了 弱引用,让线程没有对 ThreadLocal 的引用时,ThreadLocal 对象会被 GC 回收。
● ThreadLocal 被回收,但是 value 没回收造成内存泄漏
从上面可知,当 ThreadLocal被销毁后,ThreadLocal 会被 GC 回收。但若Value中存的也是一个引用类型且是强引用,则 Value 值指向的内存空间不会随着 ThreadLocal 回收而回收,这就是 ThreadLocal 造成内存泄漏的原因
防止方式:
当 ThreadLocal 使用完毕后,一定要记得调用 remove() 方法!
当使用的是线程池,核心线程不会被退出,若没有清理 ThreadLocal,则会导致下次使用,会访问到上次使用时存下来的值。且若是重复使用,可能造成 ThreadLocalMap 存储越来越多的无用内容,造成内存占用