ThreadLLocal的学习

本文解析ThreadLocal的工作原理,探讨为何会出现内存泄露,解释为何使用弱引用以及如何避免。同时强调及时清理ThreadLocalMap的重要性,尤其是在线程池场景中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ThreadLocal的学习

1.ThreadLocal是什么?

ThreadLocal对象可以提供局部变量,每个线程Thread拥有一份自己的副本变量,每个线程Thread拥有一份自己的副本变量,多个线程互不干扰。

2.ThreadLocal的数据结构

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadlocals

也就是说每个线程都有一个自己的ThreadLocalMap

ThreadLocal.ThreadLocalMap threadLocals=null;

ThreadLocalMap中维护了k-v形式的Entry对象

其中key视为ThreadLocal,value就是我们在ThreadLocal中存储的值

ThreadLocalMap有点类似HashMap的结构,只是HashMap是由数组+链表实现的,而ThreadLocalMap中没有链表结构

注意:

key并不是ThreadLocal本身,而是它的一个弱引用(因为继承自WeakReference

由此,ThreadLocal本身是不存储值的,我们在使用其对应的set和get方法的时候都是操作的其对应的ThreadLocalMap对象。

也就是说每个线程在往ThreadLocal里存值的时候都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key。这就是线程隔离

Java的四种引用类型

Java中由值类型也有引用类型,引用类型一般是针对Java中的对象来说的。

Java为引用类型专门定义了一个类叫做Reference

Reference是跟Java垃圾回收机制息息相关的类。

  1. 强引用 Strong Reference

    Java中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用

    Object obj=new Object()
    

    我们new了一个Object对象并将其赋值给obj,这个boj就是new Object()的强引用

    强引用的特征就是只要有强引用存在,被引用的对象就不会被回收(也就是new Object()不会被回收)

  2. 软引用 Soft Reference

    只有在内存不足的情况下,被引用的对象才会被回收

    public class SoftReference<T> extends Reference<T>{
            public SoftReference(T referent) {
                /**/
            }
            public SoftReference(T referent, ReferenceQueue<? super T> q) {
                /**/
            }
    
    }
    

    SoftReference继承自Reference,有两种构造函数

    T referent:就是软引用对象

    ReferenceQueue<? super T> q:就是用来存储封装的待回收的Reference对象,eferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。

  3. 弱引用 Weak Reference

    弱引用和软引用类似,但是弱引用的对象(即被引用的对象)只要垃圾回收执行不管内存是否充足都会被回收(只有弱引用的时候

  4. 虚引用 Phantom Reference

    虚引用是最弱的引用,引用的是需要被垃圾回收的对象

    虚引用中唯一的作用就是用队列接收对象即将死亡的通知

    所以在类的定义中,get一直返回的都是null

    PhantomReference只有一个构造函数并且必须传入ReferenceQueue

    public class PhantomReference<T> extends Reference<T>{
        public PhantomReference(T referent, ReferenceQueue<? super T> q){
            /**/
        }
    }
    

    ReferenceQueue<? super T> q:就是用来存储封装的待回收的Reference对象。

    虚引用跟踪垃圾回收器(gc)收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理(这个和软引用是不一样的)

    当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。

3.ThreadLocal为什么会出现内存泄露?

在这里插入图片描述

在当前线程正在运行的时候,如果发生GC,此时ThreadLocal对象没有被其它地方强引用的时候(即只有弱引用),key指向的ThreadLocal的虚引用就会立即断开(因为弱引用指向的对象被垃圾回收了)

这时就会出现ThreadLocalMap中存在key为null的Entry

并且只要当前线程不结束,该ThreadLocalMap对象就会一直存在,无法回收(因为ThreadLocalMap还存在强引用:value本身就存着一个强引用对象)

此时就导致了内存泄露

既然会出现内存泄露为什么Entry的key还要使用弱引用?

为什么要用弱引用呢?

因为假设我们让key强引用ThreadLocal会导致该对象用永远无法gc

如何避免内存泄露?

其实ThreadLocalMap在设计时采取了一些措施来避免这种key为null、value不为null的对象占用内存

具体措施就是在我们调用ThreadLoca的set、get、remove方法时都会将这些key为null的对象清掉,避免因无法回收而导致内存泄露

如果没有及时使用remove方法会导致什么问题?

  1. 假设分配了ThreadLocal对象但是并没有执行get、set、remove方法会导致不能有效地清除null对象

  2. 因为ThreadLocal时属于某个线程的,而在使用线程池的情况下,这些线程都是可重复利用的、存活时间长的线程,因此不及时使用remove方法不仅会导致内存泄露问题,还会引发一些功能逻辑问题

    例如,B请求和A请求分配到了线程池中的同一个线程,那么他们拿到的ThreadLocal可能是一样的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值