ThreadLocal学习笔记

本文详细介绍了ThreadLocal的作用、原理、使用场景,以及如何正确管理其生命周期,包括数据共享、内存管理和在SpringBoot中的应用实例,强调了remove方法在防止内存溢出中的重要性。
摘要由CSDN通过智能技术生成

原文链接

ThreadLocal概述

线程缓存数据,封装线程缓存功能细节,方便操作。

ThreadLocal用static修饰

个人认为是方便一个请求或者一个线程,在不同的类方法之间可以共享线程变量。

ThreadLocal使用

了解再多,用错就等于零。

使用static修饰,创建ThreadLocal的实例。

public static ThreadLocal tl = new ThreadLocal();
public static ThreadLocal tl2 = new ThreadLocal();
public static ThreadLocal tl3 = new ThreadLocal();
public void fun() {
    ...//业务代码
    tl.set(xxx);//缓存内容
    Object o = tl.get();//获取缓存内容
    ...//业务代码
    tl.remove();//新手村选手必须加上
}

public修饰符根据自己需求修改,ThreadLocal可以设置缓存内容的泛型。

如果涉及跨方法调用,跟操作静态变量一样,类名.tl.get();

这么用,至少不会出现错误。

ThreadLocal应用场景

缓存数据

当某个变量需要多次调用,而这个变量需要经过复杂过程才能获取。

不需要获取全部情况的数据,只需要多次调用全部情况中少部分数据,可以每次只获取一种情况,可以使用ThreadLocal存,下次再使用时,可以先使用ThreadLocal中的数据,如果有,则返回,没有,获取然后存ThreadLocal。

看样子map也可以做类似的事情,对比

static修饰的map局部变量mapThreadLocal
全局共享方法内共享,也可参数传递线程共享

对比来看,局部变量的map可以替代ThreadLocal。

ThreadLocal部分源码

ThreadLocal.set()实际上是封装了线程缓存数据的具体实现。

以下截图均来自ThreadLocal类。

图片

图片

图片

图片

图片

Thread.currentThread(),该方法是native方法,用于获取当前线程。
ThreadLocalMap是ThreadLocal的静态内部类,通过构造函数可以了解到,ThreadLocalMap内部有个Entry数组,可以通过ThreadLocal获取下标,进而找到Entry,最终找到存储的数据。
图片

图片

查看setInitialValue可得,当缓存的数据中不存在此key时,默认是null。

ThreadLocal 线程结束

线程结束会调用Thread类中的下面截图的方法,垃圾回收时会有概率回收缓存的数据。ThreadLocal缓存数据,实际就是维护了一个Entry数组,Entry数组设计为WeakReference,大大提高回收概率。(正在运行的线程缓存的数据,不会回收)。

图片

ThreadLocal 实际使用

实际开发过程中,一般不会新建个线程,执行完成就结束了。

一般情况下,要么是处理web请求,要么是多线程处理数据中使用。

线程池的目的是避免重复的销毁重建,意味着,线程实际上不容易销毁,那上面的clearReferences方法就不会调用。

处理web请求:

不同的请求,不同参数,要存的数据可能是不一样的,避免异常,必须在不再使用ThreadLocal后调用ThreadLocal.remove(),清除线程中存的数据。

线程池:

与web请求类似,如果不希望线程之间共享数据,需要调用ThreadLocal.remove()

remove:

只要是不希望线程之间共享数据的,最好调用remove方法,

好处:可以使服务充分利用内存,调用remove后,线程缓存的数据可以在gc时有机会被回收,而不调用,则没有机会被回收(static修饰的ThreadLocal实验);减小内存溢出的风险,并发高或者线程池最大线程数大的情况下,执行结束的线程不及时释放缓存,导致缓存数据积增,造成内存溢出。

高并发或线程多的情况下,即便调用remove,也有可能造成内存溢出,ThreadLocal存的数据要尽可能的简洁。(这种情况下,只要缓存的数据不大,个人认为与ThreadLocal关系不大,就是用别的本地缓存也会溢出)

实验:

springboot项目,增加一个简单的接口,使用ThreadLocal存数据。

@RestController
public class TestController {

    public static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
    
    @GetMapping("hello")
    public void hello() {
        if ("http-nio-8080-exec-1".equals(Thread.currentThread().getName())) {
            System.out.println("before" + Thread.currentThread().getName() + (threadLocal.get() == null ? "null" : threadLocal.get().toString()));
        }

        threadLocal.set(new byte[1024 * 1024]);
        if ("http-nio-8080-exec-1".equals(Thread.currentThread().getName())) {
            System.out.println("after" + Thread.currentThread().getName() + threadLocal.get().toString());
            //threadLocal.remove();
        }
    }
}

说明:

简单的web请求,我的端口是8080,经过之前的调用得出线程名称都为http-nio-8080-exec-x形式,为了排除干扰,所以加了过滤。

连续多次调用接口结果:

threadLocal.remove()注释后

图片

可以发现,如果不调用remove方法,会造成两个请求共用缓存信息的情况。也可以发现,后面的请求会覆盖前面的请求缓存的数据。

threadLocal.remove()解除注释后

图片

发现线程之间不再共享数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值