乐趣区

关于java: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 + ":" + local.get());
        // 革除内存中的本地变量
        localVar.remove();}
    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("革除后:" + local.get());  
            }
        },"A").start();
        Thread.sleep(1000);
 
        new Thread(new Runnable() {public void run() {ThreadLocaTest.local.set("xdclass_B");
                print("B");
                System.out.println("革除后" + localVar.get());
            }
        },"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.getxxx 就能够了

如何应用 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 类来实现的,它的生命周期比软援用还要短(一个比一个短),在进行垃圾回收的时候,不论内存的空间够不够都会回收掉这对象

应用场景:如果一个对象只是偶然来应用的话,心愿在应用的时候可能随时的获取,然而呢,也不想影响到该对象的垃圾收集,这时候就能够思考到应用弱援用来指向这个对象。

讲了这么多还是没有讲到这个面试题,当初就来讲讲这个面试题该怎么样答复

ThreadLocal 为什么是 WeakReference 呢?

第一、如果是强援用的话,即便 ThreadLocal 的值是为 null,然而的话 ThreadLocalMap 还是会有 ThreadLocal 的强援用状态,如果没有手动进行删除的话,ThreadLocal 就不会被回收,这样就会导致 Entry 内存的透露

第二、如果是弱援用的话,援用 ThreadLocal 的对象被回收掉了,ThreadLocalMap 还保留有 ThreadLocal 的弱援用,即便没有进行手动删除,ThreadLocal 也会被回收掉。value 在下一次的 ThreadLocalMap 调用 set/get/remove 办法的时候就会被革除掉。

退出移动版