ThreadLocal
ThreadLocal 概念
ThreadLocal 也叫线程局部变量,目的是在线程并发时,解决变量共享问题。
每一个 Thread 线程对象中都有一个 ThreadLocalMap 对象,这个对象存储了一组以指向 ThreadLocal 变量的弱引用为键,以本地线程变量为值的 K-V 键值对。ThreadLocal 相当于当前线程的 ThreadLocalMap 的访问入口,每一个 ThreadLocal 对象包含了一个独一无二的 ThreadLocalHashCode ,使用这个 key 就可以在线程内部的 Map 对象中找到对应的线程本地变量。
但不当的使用 ThreadLocal 容易出现内存泄漏、脏数据、共享对象更新等问题。
ThreadLocal 作用
局部变量可以在方法内各个代码块中进行传递,类变量可以在类中方法间传递,而一个复杂的线程可能可能需要调用很多方法来实现某种功能,这时候就需要 ThreadLocal 变量来传递线程内的变量,通过它可以实现跨类,跨方法传递数据。不仅如此,TheadLocal 还可以通过设置线程的构造方法最后一个参数,使得线程局部变量在父线程和子线程之间传递
ThreadLocal 对象通常是由 private static 修饰的,因为都需要复制进入本地线 程,所以非 static 作用不大,如果说一个 ThreadLocal 是非静态的,属于某个线程实例类,那就失去了线程间共享的本质属性
那为什么不通过方法参数传递变量?
这样会导致类与类之间的紧耦合,特别是在跨多个方法的时候
为什么 TheadLocal 常作为私有静态使用?
如果 TheadLocal 不作为静态变量,而属于某个线程的实例类,就失去了线程之间共享的本质属性,同时 ThreadLocal 的作用只是标识变量以及访问 ThreadLocalMap ,多份的 ThreadLocal 没有必要
TheadLocal 实践场景
透传全局上下文
解决并发下 SimpleDateFormat 的线程不安全
为什么 SimpleDateFormat 不是线程安全的?
因为 SimpleDateFormat 中使用一个成员变量保存时间,设置并使用该时间的时候并没有同步
TheadLocal 副作用
ThreadLocal 的主要问题是会产生脏数据和内存泄漏。这两个问题通常是在线程池的线程中使用 ThreadLocal 引 发的,因为线程池有线程复用和内存常驻两 个特点。
脏数据:线程复用会产生脏数据。由于线程池会重用 Thead 对象,那么与 Thead 绑定的类的静态属性 TheadLocal 也会被重用,所以在 run() 方法中显式调用 remove() 清理线程相关的 TheadLocal 消息内存泄漏:不进行 remove() 操作, 线程执行完成后,通过 ThreadLocal 对象持有的对象是不会被释放的。因为线程持有 ThreadLocalMap 的引用,ThreadLocalMap 又持有 value 的引用
为什么 TheadLocal 存在内存泄漏?
因为线程到 ThreadLocalMap 再到 Entry 再到 局部变量存在强引用的关系,当线程不死亡也不对局部变量进行 remove 操作,局部变量是不会被回收的。
虽然在 TheadLocalMap 中,Entry 的 key 是由一个虚引用指向相应的 TheadLocal 对象,当 ThreadLocal 变量没有强引用指向它时,指向 ThreadLocal 会被下一次 GC 回收,但尽管如此对于 Entry 中 Value 还是存在强引用是不会被回收的。更何况 ThreadLocal 变量一般由 private static 修饰,生命周期至少不会随着线程结束而结束。
总之就是,线程中的局部变量 Value 无法在在该回收时自动回收,线程持续执行或者呆在线程程池中占用不必要的内存,导致内存泄漏
以上参考: 《码出高效:Java开发手册》
ThreadLocal 内存泄漏 Demo 如下:
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class main {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int temp=cin.nextInt(); //等待一个输入 方便连接 VisualVM
ExecutorService service= Executors.newFixedThreadPool(10000);
for (int j = 0; j < 1000; j++) {
service.execute(new Test());
}
temp=cin.nextInt();
service.shutdown();
}
}
class Test implements Runnable {
private static ThreadLocal<BigClass> localValue=new ThreadLocal<>();
@Override
public void run() {
localValue.set(new BigClass());
localValue.remove(); //不显示使用 remove 会发生内存泄漏
}
}
class BigClass{
private Long[] a = new Long[1024*1024];
}