ThreadLocal

入门学习

简介

ThreadLocal 是 Thread 的 局部变量,意思是说,ThreadLocal 中填充的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal 为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。

ThreadLocal 是一个弱引用,当为 null 时,会被当成垃圾回收 。

ThreadLocal 并不是用来解决共享资源问题的。虽然 ThreadLocal 确实可以用于解决多线程情况下的线程安全问题,但其资源并不是共享的,而是每个线程独享的,所以避免了同步操作。但是如果你放到 ThreadLocal 中的资源是一个 共享资源 的话,比如 static,那么用了 ThreadLocal 也避免不了同步问题。

Threadlocal 的好处

  • 在进行对象跨层传递的时候,使用 ThreadLocal 可以避免多次传递,

  • 线程间数据隔离,

  • 进行事务操作,用于存储线程事务信息。

  • 数据库连接,Session会话管理,

Thread、ThreadLocal、ThreadLocalMap 之间的关系

ThreadLocalMap 是 ThreadLocal 的一个静态内部类,里面定义了 Entry 来保存数据。而且是继承的弱引用。ThreadLocal 就相当于一个工具类。

然后每个线程都有一个 ThreadLocalMap threadLocals,然后每个 Thread 可以调用多个静态的 ThreadLocal 对象,里面 get、set 的时候都是对于当前 ThreadLocal 而言的。

然后一般 ThreadLocal 都是作为静态资源,就是说多个线程将同一个 ThreadLocal 对象作为该线程内的 ThreadLocalMap 的 key。ThreadLocal中包含了ThreadLocalMap,然而ThreadLocalMap的对象是在Thread中的。

  • 在 Entry 内部使用 ThreadLocal 作为 key,使用我们设置的 value 作为 value。所以 set 方法,就是对当前线程的 ThreadLocalMap 进行设置 k:v,key 是当前 ThreadLocal,vale 是 set 的值,

  • 则 get 方法就是从当前 Thread 获取到 ThreadLocalMap,然后从里面获取当前 ThreadLocal 的 value。 值得一提的是,在 ThreadLocal 的 get(),set() 的时候都会清除线程 ThreadLocalMap 里所有 key 为 null 的 Entry。

ThreadLocal 的内存泄漏问题,Entry 内存泄漏

ThreadLocal内存泄漏的原因主要是因为ThreadLocal中包含了ThreadLocalMap,然而ThreadLocalMap的对象是在Thread中的,如果Thread没有结束,则ThreadLocalMap一直不会释放,假如ThreadLocalMap中设置了很多值,而且没有手动设置remove(),则可能会造成内存泄露。网上有些网友的答案是因为ThreadLocal作为一个弱引用的key然后造成每次GC的时候会回收掉ThreadLocal,导致无法访问value,然后造成了内存泄漏,这是完全错误的,其实和弱引用没有关系。

先思考一下,如果每次GC都把ThreadLocal给回收掉,那么业务代码在运行的时候出现GC,ThreadLocal被回收之后,业务代码就会获取不到值,这样就会出现问题。弱引用的定义确实是在GC的时候就会被回收掉,如果ThreadLocal又被其它强引用指向了,则它是不会被回收的,想想我们在使用ThreadLocal的时候,我们是怎么定义并使用他的呢?

我们一般都是用 static 修饰的,可以看到threadLocal可以定义为一个全局的静态变量或者是一个局部变量,threadLocal是强引用,不管怎么样它都不会被回收掉,只是又将它的引用又给到了ThreadLocalMap中的Entry.key中,虽然这个key是弱引用,但是ThreadLocal对象是不会被回收的。

如果一个线程Thread结束了,那么Thread里面的threadLocals将会被回收掉,也就是ThreadLocalMap数据结构会被回收掉,也就不会出现内存泄漏的问题。那出现内存泄露的情况是什么呢?

当Thread一直没有结束时,Thread中的threadLocals就不会被回收,threadLocals里面存储的Entry如果不手动删除的话,就会一直存在这个threadLocals里面,所以就会出现内存泄漏的问题,通常在线程池的情况下,一个Thread会使用很长时间,如果在使用的过程中,一直向里面设置Entry,也就是key = ThreadLocal 和 value=业务对象,如果一个线程的任务执行完毕之后,没有手动设置remove()方法释放掉这个Entry的话,那么Thread的threadLocals中的Entry将会一直膨胀,一直停留在Thread中的threadLocals中,造成内存泄漏,所以一定要手动设置remove()。

那这会延伸出来另外一个问题,为什么使用弱引用?

弱引用的作用就是当出现GC的时候会回收这些弱引用的对象,如果有些业务不是定义的全局的静态变量而是局部的变量,例如:通过ThreadLocal保存一些在整个线程中全局都可以使用的变量,减少方法与方法调用的参数传递。此时当一个任务执行完成之后,可以将ThreadLocal设置成为null,局部变量的强引用就会失效,存在Map中的Entry的key只有弱引用,如果不进行清理的话,则会出现内存泄漏的问题,此时出现GC的时候就会回收掉ThreadLocal对象,也就是说ThreadLocal是尽量的去避免内存泄漏的问题。但是解决不了根本的问题,所以需要开发人员手动去调用remove()方法才能够彻底解决内存泄漏的问题。

实践

实践1

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UserDetailUtils {
    private static ThreadLocal<UserDetail> tl = new ThreadLocal<>();
​
    public static void set(UserDetail userDetail) {
        tl.set(userDetail);
    }
​
    public static UserDetail get() {
        return tl.get();
    }
​
    public static void remove() {
        tl.remove();
    }
}

  • 11
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值