• ThreadLocal详解


    两大使用场景——ThreadLocal的用途

    • 每个线程需要一个独享的对象(通常是工具类例如:SimpleDateFormat和Random),让某个需要用到的对象在线程间隔离
    • 每个线程内需要保存全局变量,可以让不同的方法使用,避免参数传递的麻烦

    使用ThreadLocal带来的好处

    • 达到线程安全
    • 不需要加锁,提高执行效率
    • 更高效地利用内存、节省开销:相比于每个任务都新建一个SimpleDateFormat,显然用ThreadLocal可以节省内存和开销。
    • 免去传参的繁琐:无论是场景一的工具类还是场景二的全局变量,都可以在任何地方通过ThreadLocal直接拿到,不需要每次传递同样的参数。ThreadLocal使得代码耦合度更低,更优雅。

    主要方法

    • T initialValue():初始化
      • 该方法会返回当前ThreadLocal对应的初始值,这是一个延迟加载,只有在get的时候才会调用initialValue方法
      • 线程在get之前调用了set方法,就不会调用initialValue方法
      • initialValue只会调用一次,除非使用了remove方法后调用get
      • 不重写initialValue方法,返回值是null。一般使用匿名内部类的方式来重写initialValue方法。
    • void set(T t):为这个ThreadLocal设置一个新值
    • T get():先取出当前线程的ThreadLocalMap,然后调用map.getEntry方法,把本ThreadLocal的引用作为参数传入,取出map中属于本ThreadLocal的value。如果是首次调用get(),则会调用initialValue来生成这个值,并创建一个map对象,并将生成的这个值与ThreadLocal类生成一个entry存入这个map中。
    • void remove():删除对应这个ThreadLocal的值。

    ThreadLocal原理

    • ThreadLocalMap类
      ThreadLocalMap类,也就是Thread.threadLocals
      ThreadLocalMap类是每个线程Thread类里面的变量,里面最重要的是一个键值对数组Extry[] table,可以认为是一个map,键值对:

      • 键:这个ThreadLocal
      • 值:实际需要的成员变量,比如SimpleDateFormat对象。
      • 处理冲突:ThreadLocalMap采用的是线性探测法,也就是如果发生冲突,就继续找下一个空位置,而不是用链表拉链。
    • 两种场景都是调用的map.set()类设置值,也就是说,两种场景都会对应到ThreadLocalMap的一个Entry,只不过是起点和入口不一样。

    ThreadLocal注意点

    • 内存泄漏

      • 什么是内存泄漏:某个对象不再有用,但是占用的内存却不能被回收。
      • Key的泄漏:ThreadLocalMap中的Entry继承自WeakReference,是弱引用,弱引用的特点是,如果这个对象被弱引用关联(没有任何强引用关联),那么这个对象就可以被回收
      • ThreadLocalMap的每个Entry都是一个对key的弱引用,同时每个Entry都包含了一个对value的强引用。正常情况下,当线程终止了,保存在ThreadLocal里的value会被垃圾回收,因为没有任何强引用了。但是如果线程不被终止(比如线程需要保持很久,例如线程池),那么key对应的value就不能被回收。 因为有调用链:Thread → ThreadLocalMap → Entry(key为null) → Value。会导致value无法回收,就可能会出现OOM。JDK已经考虑到了这个问题,所以在set,remove,rehash方法中会扫描key为null的Entry,并把对应的value置为null,这样value对象就可以被回收。但是如果一个ThreadLocal不被使用,那么实际上这些方法也不会被调用,就依然会有内存泄漏的隐患
      • 如何避免内存泄漏(阿里规约)
        调用remove方法,就会删除对应的Extry对象,可以避免内存泄漏,所以使用完ThreadLocal之后,应该调用remove方法。
    • ThreadLocal空指针异常问题

      • 若未初始化,get会返回一个null,若方法返回值涉及到装箱拆箱就可能导致空指针异常,并不是ThreadLocal的问题。
    • 共享对象

      • 如果在每个线程中ThreadLocal.set()保存的东西本来就是多线程共享的同一个对象,比如static对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
    • 如果可以不使用ThreadLocal就解决问题,那么就不要强行使用,比如任务数很少的时候,在局部变量中可以新建对象就可以解决问题,那么就不需要使用到ThreadLocal

    • 优先使用框架支持,而不是自己创造。

  • 相关阅读:
    CS224W 8 GNN Augmentation andTraining
    ESP32建立TCP连接
    Spring中的作用域Bean Scope
    高并发扣款,如何保证结果一致性
    JVM:(五)运行时数据区之虚拟机栈
    解禁谷歌等浏览器禁止网站使用麦克等媒体设备
    SpringBoot 统一功能处理
    nginx隐藏版本号和标识
    煤矿皮带运行视频监控系统
    基础算法(二)| 归并排序
  • 原文地址:https://blog.csdn.net/Laoddaaa/article/details/126749611