ThreadLocal的介绍+经典应⽤场景
作者⼩三是刚刚毕业不久全栈⼯程师,写的技术⽂章基本上是学习过程中笔记整理⽽来,⼤家看了之后如果喜欢可以给⼩弟点点赞哦。
例外⼩弟还有个程序员交流,欢迎各位⼤佬来摸鱼哈。
什么是ThreadLocal
ThreadLocal⼜叫做线程局部变量,全称thread local variable,它的使⽤场合主要是为了解决多线程中因为数据并发产⽣不⼀致的问题。ThreadLocal为每⼀个线程都提供了变量的副本,使得每⼀个线程在某⼀时间访问到的并不是同⼀个对象,这样就隔离了多个线程对数据的数据共享,这样的结果⽆⾮是耗费了内存,也⼤⼤减少了线程同步所带来的性能消耗,也减少了线程并发控制的复杂度。
总的来说:ThreadLocal适⽤于每⼀个线程需要⾃⼰独⽴实例,⽽且实例的话需要在多个⽅法⾥被使⽤到,也就是变量在线程之间是隔离的但是在⽅法或者是类⾥⾯是共享的场景
那ThreadLocal和Synchronized⼜有什么区别呢?
虽然ThreadLocal和Synchonized都⽤于解决多线程的并发访问,但是它们之间还是会有⼀些本质上的区别的:
Synchronized是利⽤锁的机制,使得变量或者是代码块在某⼀时刻⾥只能被⼀个线程来进⾏访问。ThreadLocal是为每⼀个线程都提供了⼀个变量的副本,这样就是的每⼀个线程在某⼀时刻⾥访问到的不是同⼀个对象,这样就隔离了多个线程对数据的数据共
享,Synochronized正好相反,可以⽤于多个线程之间通信能够获得数据共享。
注:ThreadLocal不可以使⽤原⼦类型,只能使⽤Object类型
ThreadLocal的简单使⽤
public class ThreadLocaTest {
private static ThreadLocal<String> local = new ThreadLocal<String>();
static void print(String str) {
//打印当前线程中本地内存中变量的值
System.out.println(str + " :" + ());
//清除内存中的本地变量
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
public void run() {
ThreadLocaTest.local.set("xdclass_A");
print("A");
//打印本地变量
System.out.println("清除后:" + ());
}
},"A").start();
Thread.sleep(1000);
new Thread(new Runnable() {
public void run() {
ThreadLocaTest.local.set("xdclass_B");
print("B");
System.out.println("清除后 " + ());
}
},"B").start();
}
}
运⾏后可以看到xdclass_A的值为null,xdclass_B的值也为null,表明了两个线程都分别获取了⾃⼰线程存放的变量,他们之间获取到的变量不会错乱。
ThreadLocal核⼼应⽤的场景介绍
ThreaLocal作⽤在每个线程内都都需要独⽴的保存信息,这样就⽅便同⼀个线程的其他⽅法获取到该信息的场景,由于每⼀个线程获取到的信息可能都是不⼀样的,前⾯执⾏的⽅法保存了信息之后,后续⽅法可以通过ThreadLocal可以直接获取到,避免了传参,这个类似于全局变量的概念。⽐如像⽤户登录令牌解密后的信息传递、⽤户权限信息、从⽤户系统中获取到的⽤户名
如上图所⽰,就好⽐如线程A的⽅法⼀创建了变量A,⽅法⼆是跟⽅法⼀在同⼀个线程内,那么创建的变量A就是共享的。
#⽤户微服务配置token解密信息传递例⼦
public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();
LoginUser loginUser = new LoginUser();
loginUser.setId(id);
loginUser.setName(name);
loginUser.setMail(mail);
loginUser.setHeadImg(headImg);
threadLocal.set(loginUser);
后续想直接获取到直接就可以了
如何使⽤ThreadLocal来解决线程安全的问题
在我们平常的SpringWeb项⽬中,我们通常会把业务分成Controller、Service、Dao等等,也知道注解@Autowired默认使⽤单例模式。那有没有想过,当不同的请求线程进来后,因为Dao层使⽤的是单例,那么负责连接数据库的Connection也只有⼀个了,这时候如果请求的线程都去连接数据库的话,就会造成这个线程不安全的问题,Spring是怎样来解决的呢?
在Dao层⾥装配的Connection线程肯定是安全的,解决⽅案就是使⽤ThreadLocal⽅法。当每⼀个请求线程使⽤Connection的时候,都会从ThreadLocal获取⼀次,如果值为null,那就说明没有对数据库进⾏连接,连接后就会存⼊到 ThreadLocal⾥,这样⼀来,每⼀个线程都保存有⼀份属于⾃⼰的Connection。每⼀线程维护⾃⼰的数据,达到线程的隔离效果。
ThreadLocal慎⽤的场景
第⼀点(线程池⾥线程调⽤ThreadLocal):因为线程池⾥对线程的管理都是线程复⽤的⽅法,所以在线程池⾥线程⾮常难结束,更有可能的是永远不会结束。这就意味着线程的持续时间是不可估测的,甚⾄会与JVM的⽣命周期⼀致。
第⼆点(在异步程序⾥):ThreadLocal的参数传递是不可靠的,因为线程将请求发送后,不会在等待远程返回结果就继续向下运⾏了,真正的返回结果得到以后,可能是其它的线程在处理。
第三点:在使⽤完ThreadLocal,推荐要调⽤⼀下remove()⽅法,这样会防⽌内存溢出这种情况的发⽣,因为ThreadLocal为弱引⽤。如果ThreadLocal在没有被外部强引⽤的情况下,在垃圾回收的时候是会被清理掉的,如果是强引⽤那就不会被清理。
轻松的掌握ThreadLocal底层源码解读+原理
ThreadLocal的set⽅法,⾸先Thread t =Thread.currentThread意思就是获取到当前的线程,紧接着就是获取到线程当中的属性ThreadLocalMap,然后会进⾏对ThreadLocalMap进⾏判断,如果不为空,就直接更新要保存的变量值,否则的话就创建⼀个threadLocalMap,并且赋值。
那么ThreadLocalMap这个⽅法⼜是做什么的呢?接下来我们来看⼀看,可以看出ThreadLocalMap是⼀个ThreadLocal的内部静态类,这个类的构成主要是⽤Entry来保存数据,⽽且还是继承的弱引⽤。在Entry内部⾥使⽤ThreadLocal作为key,这⾥会使⽤我们⾃⼰设置的value作为value
上⾯说完set和ThreadLocalMap⽅法了,接下来我们再来看看get⽅法是怎样的。可以看出来get的⽅法和set的⽅法很类似,也是⾸先获取到当前的线程,再接着获取到线程的ThreadLocalMap,然后对map来进⾏判断。如果map的数据为空,那么就获取存储的值。如果数据为null的话,就开始进⾏初始化,初始化的结果就是Theradlocalmap存放的值为null。
可以看出,基本都操作都有这个ThreadLocalMap,这个类没有实现map的接⼝,就是⼀个普通的java类,但是实现的类就类似于map的功能,数据⽤Entry存储,Entry继承于WeakReference,⽤⼀个键值对来存储,键就是ThreadLocal的引⽤。每⼀个线程都有⼀个ThreadLocalMap的对象,每⼀个新的线程Thread都会实例化⼀个ThreadLocalMap并赋予值给成员变量threadLocals。
【⾯试题】为什么ThreadLocal的键是弱引⽤,如果是强引⽤会有什么问题呢?
什么是弱引⽤呢?(⼩⽩请看,⼤佬请略过~)
在java⾥,除了基础的数据类型以外,其他的都为引⽤类型,⽽java根据⽣命周期的长短⼜把引⽤类型分为强引⽤、软引⽤、弱引⽤和虚引⽤。正常的情况下我们平时基本上只适⽤到了强引⽤的类型,⽽其他的引⽤类型也就在⾯试中或者阅读源码的时候才能看到。
强引⽤:像new了⼀个对象就是强引⽤ Object obj = new Object()
软引⽤的话,⽣命周期会⽐强引⽤短⼀些,是通过SoftReference类实现的,当内存有⾜够的空间,那么垃圾回收器就不会回收它;因为当JVM认为内存空间出现不⾜的时候,就会尝试回收软引⽤指定的对象,就是说在JVM会在抛出OutOfMemoryError这个异常之前,会清理软引⽤对象。
软引⽤的使⽤场景:⽐较适合⽤来实现缓存,当内存空间充⾜的时候,将缓存存放到内存当中,如果内存不⾜了就可以把缓存回收掉
弱引⽤:弱引⽤就是通过WeakReference类来实现的,它的⽣命周期⽐软引⽤还要短(⼀个⽐⼀个短),在进⾏垃圾回收的时候,不管内存的空间够不够都会回收掉这对象
使⽤场景:如果⼀个对象只是偶尔来使⽤的话,希望在使⽤的时候能够随时的获取,但是呢,也不想影响到该对象的垃圾收集,这时候就可以考虑到使⽤弱引⽤来指向这个对象。
thread技术讲了这么多还是没有讲到这个⾯试题,现在就来讲讲这个⾯试题该怎么样回答
ThreadLocal为什么是WeakReference呢?
第⼀、如果是强引⽤的话,即使ThreadLocal的值是为null,但是的话ThreadLocalMap还是会有ThreadLocal的强引⽤状态,如果没有⼿动进⾏删除的话,ThreadLocal就不会被回收,这样就会导致Entry内存的泄漏
第⼆、如果是弱引⽤的话,引⽤ThreadLocal的对象被回收掉了,ThreadLocalMap还保留有ThreadLocal的弱引⽤,即使没有进⾏⼿动删除,ThreadLocal也会被回收掉。value在下⼀次的ThreadLocalMap调⽤set/get/remove⽅法的时候就会被清除掉。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论