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 | 局部变量map | ThreadLocal |
---|---|---|
全局共享 | 方法内共享,也可参数传递 | 线程共享 |
对比来看,局部变量的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()解除注释后
发现线程之间不再共享数据。