ThreadLocal 是什么?有哪些使用场景?

2 个回答

  • hello
    用眼睛是看不到事物的本质的,只有心灵才能够洞察一切。
    从字面意思上,ThreadLocal会被理解为线程本地存储,就是对于代码中的一个变量,每个线程拥有这个变量的一个副本,访问和修改它时都是对副本进行操作。 ### 使用场景: ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。(例如:方法直接调用时传递的变量过多,为了代码简洁性,可以使用ThreadLocal,在前一个方法中,将变量进行存储,后一个方法中取,进行使用。) ```java public class A { // 每个线程本地副本初始化 private static ThreadLocal <UserData> threadLocal = new ThreadLocal <>(). withInitial (() -> new UserData ()); public static void setUser (UserLogin user){ if (user == null ) return ; UserData userData = threadLocal.get(); userData. setUserLogin (user); } public static UserLogin getUser (){ return threadLocal.get(). getUserLogin (); } } ``` ### 实现原理 就是每个Thread有一个ThreadLocalMap,类似于HashMap,当调用ThreadLocal#set()方法进行存值时,实际上是先获取到当前的线程,然后获取线程的map,是一个ThreadLocalMap类型,然后会在这个map中添加一个新的键值对,key就是我们ThreadLocal变量的地址,value就是我们存的值。ThreadLocalMap与HashMap不同的时,解决HashMap使用的是开放定址法,也就是当发现hashCode计算得到数组下标已经存储了元素后,会继续往后找,直到找到一个空的数组下标,存储键值对。 ```java //ThreadLocal实例的赋值方法 public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取线程对应的Map ThreadLocalMap map = getMap(t); //将值存入线程特有的Map中 if (map != null) //key为this就是当前ThreadLocal引用变量的地址 //value就是我们要存储的值 map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { //线程的threadLocals实例变量就是Map return t.threadLocals; } ``` ### ThreadLocal中的Entry的key使用了弱引用,为什么使用弱引用? ![image.png](https://images.codewd.com/8e559a721687a191a5c7cd06a586c12e.png) 首先在上面类A的代码中,类A中有一个ThreadLocal类型的变量 它们的引用链如下: > ThreadLocal变量所在的类的实例(代码中A的实例)->ThreadLocal 执行代码的线程->线程独有的ThreadLocalMap->引用的key就是ThreadLocal 可以看到ThreadLocal变量不仅被所在的类A的实例所引用,还被执行的线程所引用, 1.如果使用强引用,也就是线程对ThreadLocal变量是强引用,那么即便实例A被回收了,只要线程还没有被回收,线程的ThreadLocalMap还会引用这个key(也就是这个ThreadLocal遍历),导致这个key 没有被回收,造成内存泄露。 2.如果使用弱引用,不会影响key的回收,也就是不会影响引用了ThreadLocal的实例对象的回收。 但是即便使用弱引用,ThreadLocalMap对value的引用是强引用(一边value是局部变量,也不能用弱引用,那样在用到的时候就会被),但是value依然不会被回收,会造成内存泄露。 通常来说,value回收的时机有两个: 1.我们在用完ThreadLocal后,应该遵循规范手动调用ThreadLocal#remove()对键值对value释放,这样可以使value被回收。 2.此线程在其他对象中使用ThreadLocal对线程ThreadLocalMap进行set()和get()时,由于需要进行开放定址法进行探测,会对沿途过期的键值对(就是key为null的键值对)进行清除。以及set()方法触发的cleanSomeSlots()方法对过期键值对进行清除。 [《一篇文章,从源码深入详解ThreadLocal内存泄漏问题》](https://www.jianshu.com/p/dde92ec37bd1)
    发布于 2021-11-20 18:48 •IP属地 甘肃
    举报
    评论
    收藏

  • java小生
    见过花就好,何必在意花属于谁。
    线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
    发布于 2021-07-01 20:07 •IP属地 安徽
    举报
    评论
    收藏