【JUC并发编程】Threadlocal底层原理分析(内存溢出OOM/ 内存泄漏/ 强、软、弱、虚引用实现区别)


ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定,Threadloca适用于在多线程的情况下,可以实现传递数据,实现线程隔离。


1. Threadlocal基本API

1.New Threadlocal();—创建Threadlocal
2.set 设置当前线程绑定的局部变量
3.get 获取当前线程绑定的局部变量
4.remove() 移除当前线程绑定的变量

2. Threadlocal简单用法

1.New Threadlocal();—创建Threadlocal
2.set 设置当前线程绑定的局部变量
3.get 获取当前线程绑定的局部变量
4.remove() 移除当前线程绑定的变量

public class Test003 {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            threadLocal.set("this is a demo");
            System.out.println("成功设置threadLocal");
        });
        thread.start();
        thread.join();
        System.out.println(Thread.currentThread().getName() + "," + threadLocal.get());
    }
}

根据子线程 在Threadlocal存放局部变量,但是获取的时候是主线程,所以无法获取。

3. Threadlocal应用场景

  1. Spring事务模板类
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  2. 获取httprequest
    HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
  3. Aop调用链传递参数
    LCN/seata 生成全局的id

4. Threadlocal与Synchronized区别

Synchronized与Threadlocal 都可以实现多线程访问,保证线程安全的问题。
A. Synchronized采用当多个线程竞争到同一个资源的时候,最终只能够有一个线程访问,采用时间换空间的方式,保证线程安全问题

B.Threadlocal在每个线程中都自己独立的局部变量, 空间换时间,相互之间都是隔离,相比来说Threadlocal效率比Synchronized效率更高。

5. Threadlocal真实应用场景

例如:在aop中生成全局的id,目标方法获取aop中生成的全局id;
Aop拦截目标方法 生成全局的id

@Aspect
@Component
@Slf4j
public class DemoAop {
    @Autowired
    private GlobalIDContextholder globalIDContextholder;

    /**
     * 切入点
     */
    @Pointcut("execution(public * com.demo.service.*Service.*(..))")
    public void log() {
    }

    /**
     * 前置操作
     *
     * @param point 切入点
     */
    @Before("log()")
    public void beforeLog(JoinPoint point) {
        UUID globalId = UUID.randomUUID();
        globalIDContextholder.set(globalId.toString());
    }
}

@RestController
public class DemoService {
    @Autowired
    private GlobalIDContextholder globalIDContextholder;

    @GetMapping("/doDemo")
    public String doDemo() {
        return "从aop中获取的全局id:" + globalIDContextholder.getId();
    }
}

@Component
public class GlobalIDContextholder {

    private ThreadLocal<String> globalIds = new ThreadLocal<String>();

    public String getId() {
        return globalIds.get();
    }

    public void set(String globalId) {
        globalIds.set(globalId);
    }
}

6. 什么是内存溢出

指程序在申请内存时,没有足够的内存空间提供其使用,出现 out of memory。 OOM溢出,一般解决办法:加内存。

eg:堆内存 1gb 申请内存2gb

7. 什么是内存泄漏

指程序在申请内存后,无法释放已申请的内存空间,内存泄露会导致内存被占光,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

8. 强、软、弱、虚引用实现区别

  1. 强引用: 当内存不足时,JVM开始进行GC(垃圾回收),对于强引用对象,就算是出现了OOM也不会对该对象进行回收,死都不会收。

  2. 软引用:当系统内存充足的时候,不会被回收;当系统内存不足时,它会被回收,软引用通常用在对内存敏感的程序中,比如高速缓存就用到软引用,内存够用时就保留,不够时就回收。
    软引用对象它是在申请堆内存不足时,才会触发清理软引用对象;

  3. 弱引用:弱引用需要用到java.lang.ref.WeakReference类来实现,它比软引用的生存周期更短。对于只有弱引用的对象来说,只要有垃圾回收,不管JVM的内存空间够不够用,都会回收该对象占用的内存空间。

  4. 虚引用:虚引用需要java.lang.ref.Phantomreference类来实现。顾名思义,虚引用就是形同虚设。与其它几种引用不同,虚引用并不会决定对象的生命周期。所以, 虚引用通常用来跟踪对象被垃圾回收的活动。
    比如,虚引用可用于在某些对象超出范围以执行某些资源清理时通知您。

9. Threadlocal原理分析

  1. 在每个线程中都有自己独立的ThreadLocalMap对象,中Entry对象。
  2. 如果当前线程对应的的ThreadLocalMap对象为空的情况下,则创建该ThreadLocalMap对象,并且赋值键值对。 Key 为 当前new ThreadLocal对象,value 就是为object变量值。

Set方法

  1. 获取到当前的线程
  2. 获取到当前线程中的threadLocals
  3. threadLocals类型ThreadLocalMap 底层基于Entry对象封装
  4. Entry对象 key=ThreadLocal value缓存的变量的值
  5. 一个线程对应一个独立ThreadLocalMap 一个ThreadLocalMap k==ThreadLocal value缓存变量的值
    Get方法实现:
  6. 获取当前线程对应的threadLocals
  7. 在根据具体threadLocal对象获取value值

10. Threadlocal产生内存泄漏问题

1.因为每个线程中都有自己独立的ThreadLocalMap对象,key为ThreadLocal,value 是为变量值。
2.Key为ThreadLocal 作为Entry对象的key,是弱引用,当ThreadLocal指向null的时候,Entry对象中的key变为null,该对象一直无法被垃圾收集机制回收,一直占用到了系统内存,有可能会发生内存泄漏的问题。

11. 如何避免Threadlocal内存泄漏问题

  1. 可以自己调用remove方法将不要的数据移除避免内存泄漏的问题;
  2. 每次在做set方法的时候会清除之前 key为null;
  3. Threadlocal为弱引用

12. Threadlocal采用弱引用而不是强引用

如果key是为强引用: 当我们现在将ThreadLocal 的引用指向为null,但是
每个线程中有自己独立ThreadLocalMap还一直在继续持有该对象,但是我们
ThreadLocal 对象不会被回收,就会发生ThreadLocal内存泄漏的问题。

如果key是为弱引用:
当我们现在将ThreadLocal 的引用指向为null,GC触发回收垃圾的情况下,Entry 中的key指向为null,但是下次调用set方法的时候,会根据判断如果key空的情况下,直接删除,有可能会发生Entry 发生内存泄漏的问题。

不管是用强引用还是弱引用都是会发生内存泄漏的问题。
弱引用中不会发生ThreadLocal内存泄漏的问题。

但是最终根本的原因Threadlocal内存泄漏的问题,产生于ThreadLocalMap与
我们当前线程的生命周期一样,如果没有手动的删除的情况下,就有可能会发生内存泄漏的问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超级码里喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值