ThreadLocal 言简意赅

基本概念

  • ThreadLocal 的作用是 每个线程可以存储自己的 局部变量,可以解决一些并发问题,是一种 空间换时间 的思想。
  • 当调用 ThreadLocal 的 set()、get()、remove() 方法的时候,都会去获取 Thread 类中的 ThreadLocalMap 成员变量;
  • 真正存储数据的是 ThreadLocal 里面的内部类 ThreadLocalMap 里面的 Entry[] 数组;这个 map 的 key 是 ThreadLocal,是一个 弱引用;然后 key 和 value 会封装成一个 Entry 对象,通过 ThreadLocal 的哈希值做一定的运算 得到 Entry[] 数组中的位置。

在这里插入图片描述
基本使用

  • 常用方法有:init、set、get、init、remove
public class ThreadLocalTest {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 100);

    public static void main(String[] args) {
        // 在 main 线程中 set 新值并打印
        threadLocal.set(520);
        System.out.println(Thread.currentThread().getName() + " --->" + threadLocal.get());

		// 测试线程之间的 隔离性
        new Thread(()-> System.out.println(Thread.currentThread().getName() + " --->" + threadLocal.get()))
        .start();
    }
}

运行结果如下:在这里插入图片描述

源码分析&常见问题

在这里插入图片描述
在这里插入图片描述

注意:在 Entry 对象中,只有 key 是弱引用,虽然 Entry 继承了 WeakReference,不要以为整个 Entry 都是弱引用,super(k) 看到没!!!而且 WeakReference 的泛型也是 <ThreadLocal<?>>!!!

ThreadLocal 会造成内存泄漏吗?

  • 在 线程池中 如果使用不当,会造成内存泄漏;
  • 在单线程下,线程不会复用,相当于用完后 Thread 对象指向 ThreadLocal 的引用就没了,而 Entry 对象中的 key 又是 弱引用,所以就可以一起回收。
  • 但在线程池中,线程会复用,相当于 Thread 对象指向 ThreadLocal 的引用还在,但 key 又是 弱引用,在 gc 时就会导致 key 变为 null,但上一个线程 Entry 对象中的 Value 还在,如果存进去的值 很大,久而久之,就会导致 内存泄漏,像 SprintBoot 项目中,每个请求都会分配一个 Tomcat 线程池中的线程,这种场景下就要特别关注内存泄漏的问题。
  • (避免方法看下面)

ThreadLocalMap 的 key 为什么使用 弱引用?

  • 首先说一下弱引用的作用:弱引用 的对象,在 gc 时都会被回收,不管内存够不够。
  • key 使用 弱引用 可以避免一些 内存泄漏的情况;但如果是在 多线程下,就要通过要调用 remove() 方法了;
  • key 指向的是 ThreadLocal 对象,当栈帧被弹出时,如果这个 key 是强引用,就会导致 ThreadLocal 对象无法被 gc 回收,而如果这个 key 是弱引用的话,gc 时就可以一起回收了。

【注意:导致内存泄漏的对象是 ThreadLocal 对象无法回收】

怎么避免内存泄漏?

  • 最好方式是 在使用结束后,手动调用 remove() 方法,在 SpringBoot 中,可以在拦截器结束的方法中统一处理。
  • 还可以把 ThreadLocal 对象定义为 全局变量,这样不会创建很多 ThreadLocal 对象,可以减少内存的占用,但不能完全避免。

ThreadLocal 的使用场景?平时有用到过吗?

  • 比如:在拦截器中,可以把前端请求头里 带过来的 token 解析出来之后,把 userId、店铺 id、手机号 等一些通用的信息存到 ThreadLocal 里面,这样业务层在获取这些通用信息的时候,就直接到一个 统一封装的工具类中去获取就可以了,就不用一个个方法的传递了;然后在 拦截器 处理结束的方法里,调用 remove() 方法清空 set 的值
  • 还有像很多框架里也使用到了 ThreadLocal,比如 Spring 的事务管理器、SpringMVC 的 HttpSession、HttpServletRequest、HttpServletResponse 都有使用到 ThreadLocal。

如果在同一个线程中,创建两个 ThreadLocal 变量,如果这两个 ThreadLocal 变量 set 不同的值,后一个 会把前一个覆盖吗?为什么?

  • 不会;在同一个线程中,创建两个 ThreadLocal 变量,操作的 ThreadLocalMap 都是同一个线程的 Map;
  • 这个 Map 的 key 是 ThreadLocal 的哈希值通过一定的变换得到的,ThreadLocal 不同,这个 key 也就不同。

附:

java 中的四大引用:

  • 强引用:发生 OOM 也不会回收的对象,强引用 是造成 Java 内存泄露的主要原因之一。

  • 软引用:内存充足时,不会回收,系统内存不足时,才会被回收;适合做 缓存,内存够用就保留,不够用就回收。

  • 弱引用:gc 时都会被回收的对象。也适合用来做一些不重要的缓存,比如缓存本地图片,不用每次从 本地磁盘中去读取,设计思路:用 HashMap 来保存图片的路径 和 图片对象关联软引用之间的映射关系,在内存不足时,JVM 会自动回收这些缓存图片对象所占用的空间,有效避免 OOM 的问题。

  • 虚引用:通过 虚引用 在堆中获取不到对象的值,总是返回 null;虚引用 必须结合 引用队列一起使用,一般用来做 通知,在虚引用 对象被回收后,会发送一个 通知,然后在这个通知里,可以自定义一些处理逻辑。


其他

关于 InheritableThreadLocal

  • 与 ThreadLocal 不同的是:InheritableThreadLocal 会拷贝一份 父线程中 InheritableThreadLocal 的值;注意是拷贝,不是共用一个ThreadLocal
public class ThreadLocalInheritableTest {
    private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>(){
    @Override
    protected Integer initialValue() {
        // 设置初始值
        return 100;
    }
};

    public static void main(String[] args) throws InterruptedException {
        threadLocal.set(520);
        System.out.println(Thread.currentThread().getName() + " --->" + threadLocal.get());

        Thread t1 = new Thread(() -> {
				// 会拷贝一份 父线程中 InheritableThreadLocal 的值
                System.out.println(Thread.currentThread().getName() + " --->" + threadLocal.get());
                threadLocal.set(1314);
                System.out.println(Thread.currentThread().getName() + " --->" + threadLocal.get());
            });
        t1.start();
		// 保证 new 的线程先执行完
        t1.join();
        System.out.println(Thread.currentThread().getName() + " --->" + threadLocal.get());
    }
}

运行结果如下
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值