当前位置: 首页 > news >正文

深入了解ThreadLocal底层原理-高并发架构

目录

    • 什么是ThreadLocal
      • 应用场景
      • 需求实现
    • ThreadLocal核心源码解读
      • Thread 、ThreadLocal、ThreadLocalMap 三者的关系
    • 四大引用-强软弱虚类型
    • ThreadLocal内存泄漏
      • ThreadLocal为什么需要设计成弱引用?并且ThreadLocal用完需要remove呢?
      • 原因

什么是ThreadLocal

  • 全称thread local variable(线程局部变量)功用非常简单,使用场合主要解决多线程中数据因并发产生不一致问题
  • ThreadLocal 为每一个线程都提供了变量的副本,使得每个线程在某时间访问到的并不是同一个对象
  • 这样就隔离了多个线程对数据的数据共享,这样的结果是耗费了内存
  • 但是大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度
  • 总结概括起来就是:同个线程共享数据
  • 注意:ThreadLocal不能使用原子类型,只能使用Object类型

在这里插入图片描述

在这里插入图片描述

应用场景

  • ThreadLocal 用作每个线程内需要独立保存信息,方便同个线程的其他方法获取该信息的场景
  • 每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到
  • 避免了传参,类似于全局变量的概念
    • 比如用户登录令牌解密后的信息传递(用户权限信息、从用户系统获取到的用户名、用户ID)
      在这里插入图片描述

需求实现

  • 小作坊举办了德州扑克游戏,只要all in一次就可以挣钱
  • 开发一个程序,记录每个人all in 多次后,输出每个人赢得钱总额

public class PokerGame {

    ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->0);

    public void makeMoney(){
        String name = Thread.currentThread().getName();
        Integer amount = threadLocal.get();
        threadLocal.set(++amount);
        System.out.println(name+" all in");
    }
    public void showMoney(){
        String name = Thread.currentThread().getName();
        System.out.println(name+"总共挣钱:"+threadLocal.get());
    }

    public static void main(String[] args) {
        PokerGame pokerGame = new PokerGame();

        new Thread(()->{
            for(int i=0;i<10;i++){
                pokerGame.makeMoney();
            }
            pokerGame.showMoney();
        },"张三").start();

        new Thread(()->{
            for(int i=0;i<4;i++){
                pokerGame.makeMoney();
            }
            pokerGame.showMoney();
        },"李四").start();

        new Thread(()->{
            for(int i=0;i<7;i++){
                pokerGame.makeMoney();
            }
            pokerGame.showMoney();
        },"王二").start();
    }
}
  • 上文代码有啥问题
  • 是的,用完需要remove
      new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    pokerGame.makeMoney();
                }
                pokerGame.showMoney();
            } finally {
                pokerGame.threadLocal.remove();
            }

        }, "王二").start();
  • 为什么需要remove呢,继续往下看

ThreadLocal核心源码解读

Thread 、ThreadLocal、ThreadLocalMap 三者的关系

在这里插入图片描述

  • 关系
    • Thread类,里面有一个ThreadLocalMap类型的变量,变量名字叫threadLocals,是ThreadLocalMap类型
    • ThreadLocal类,里面有一个ThreadLocalMap静态内部类
      • 提供一系列方法操作ThreadLocalMap,比如get/set/remove
      • 隔离ThreadThreadLocalMap,防止直接创建ThreadLocalMap
      • 自身的get/set内部会判断当前线程是否已经绑定一个ThreadLocalMap,有就继续使用,没有就为其自身绑定
    • ThreadLocalMap 就是保存ThreadLocal的map结构,key就是ThreadLocal本身
      • 所以一个线程只能存储一个值,可以理解为JVM内部维护的一个Map<Thread, Object>
      • 当线程需要用到Object,就用【当前线程】去Map里面获取对应的Object
  • 操作
    • Thread类里面有一个ThreadLocalMap类型的变量,不能直接操作这个ThreadLocalMap
    • 需要通过【工具箱】ThreadLocal才可以操作ThreadLocalMap
    • 一个Thread只能有一个ThreadLocalMap
    • ThreadLocalMap以ThreadLocal为键存储数据
      在这里插入图片描述
  • 总结
    • ThreadLocal本身并不存储值 ( 是一个壳子 ), 它只是自己作为一个key来让线程从ThreadLocalMap获取value
    • 因为这个原理,所以ThreadLocal能够实现 “每个线程之间的数据隔离”,获取当前线程的局部变量值,不受其他线程影响

四大引用-强软弱虚类型

在这里插入图片描述

  • 强引用

    • 强引用是使用最普遍的引用,当一个对象被强引用关联后,它就不会被垃圾回收器回收
    • 比如String str = “abc”,变量str就是字符串“abc”的强引用
    • 即使在【内存不足】的情况下,JVM宁愿抛出OutOfMemoryError,也不会回收这种对象
  • 软引用

    • 软引用是用来描述一些还有用但非必需的对象,当系统内存资源不足时,垃圾回收器会回收这些对象
    • 只有当内存不足时,才会回收软引用关联的对象;当内存资源充足时,不会回收软引用关联的对象,直接调用GC也不回收
    • 一般在高速缓存中会使用,内存不够时则回收相关对象释放内存
    • 使用 SoftReference< > 包装对象就可以转换为软引用
  • 弱引用

    • 弱引用也是用来描述非必需对象,但是它的强度比软引用更弱一些,只能生存到下一次垃圾收集发生之前
    • 只要垃圾回收器工作时,无论内存是否充足,都会回收被弱引用关联的对象。
    • 使用了WeakReference类来实现弱引用
  • 虚引用

    • 最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例
    • 一个对象设置虚引用关联,目的就是能在这个对象被收集器回收时收到一个系统通知
    • 使用 PhantomReference类来实现虚引用,必需要组合使用一个引用队列ReferenceQueue
    • 当垃圾回收器要回收一个对象时,如果发现它还有虚引用,会在回收对象的内存之前,把这个虚引用加入到关联的引用队列中
      • 在虚引用对象传到它的引用队列之前会调用对象的finalize方法
引用类型被垃圾回收时刻用途生存时间
强引用从来不会对象的一般状态JVM停止运行时终止
软引用在内存不足时对象简单,缓存,文件缓存,图片缓存内存不足时终止
弱引用在gc垃圾回收时对象简单,缓存,文件缓存,图片缓存gc运行后终止
虚引用任何时候都可能被垃圾回收器回收基本不写,虚拟机使用,
用来跟踪对象被垃圾回收器回收的活动
未知

ThreadLocal内存泄漏

ThreadLocal为什么需要设计成弱引用?并且ThreadLocal用完需要remove呢?

- ThreadLocal中的一个内部类ThreadLocalMap,这个类没有实现map接口,是一个普通的Java类,但是实现的类似map的功能
- 每个数据用Entry保存,继承WeakReference 指向**ThreadLocal(所以是弱引用)键值对存储,键为ThreadLocal的自身引用**
- 每个线程持有一个ThreadLocalMap对象,每一个新的线程Thread都会实例化一个ThreadLocalMap
- 并赋值给成员变量threadLocals,使用时若已经存在threadLocals,则直接使用已经存在的对象

在这里插入图片描述

原因

  • 两个原因分析 ThreadLocalMap 的key和value造成内存泄露解决方案
  • Key问题回收
    • 如果ThreadLocal的引用丢失,ThreadLocalMap的Key如果是强引用,则没法被回收, 造成泄露
    • 所以设计成弱引用,这个时候触发GC时,Key必定会被回收
    • 这个操作是ThreadLocal自身设计进行了解决
  • Value问题回收
    • 由于Key是弱引用被回收了,然后key是null,但是value是强引用对象没法被回收和访问,就导致内存泄露
    • 所以用完需要remove相关的value,这个操作需要开发人员进行操作
  • 开发中需要注意的点
    • 常规使用的线程,如果线程对象结束被回收,则上面的key和value都可以被回收
    • 但是在实际业务里面多数是使用线程池,就导致线程不能被回收,从而如果没remove对应的值,则会导致OOM
    • 常规set/get方法里面也会清除key为null的entry对象的方法,但实际开发还是需要直接调用remove方法删除

相关文章:

  • LLM2CLIP论文学习笔记:强大的语言模型解锁更丰富的视觉表征
  • Hot100 动态规划
  • 【Java 面试 八股文】JVM 虚拟机篇
  • 三数之和:经典问题的多种优化策略
  • dlib 安装 comfy 节点确实处理
  • CentOS系统安装NFS
  • 计算机视觉:经典数据格式(VOC、YOLO、COCO)解析与转换(附代码)
  • 实战技巧:如何快速提高网站收录的多样性?
  • LangChain构建行业知识库实践:从架构设计到生产部署全指南
  • 【过程控制系统】第一章 过程控制系统的设计和发展趋势,确定系统变量和控制方案
  • 医疗AI领域中GPU集群训练的关键技术与实践经验探究(上)
  • 深入理解C语言中的枚举类型:基础、应用与最佳实践
  • 基于PSO粒子群优化的能源供应方,光伏发电,EV充电三方交易策略博弈算法matlab仿真
  • Node.js中如何修改全局变量的几种方式
  • Spring5框架八:整合Mybatis
  • 11套免费web登录页面模板分享
  • 14.10 Auto-GPT 记忆系统架构设计:实现智能体的长期记忆与经验复用
  • 全面汇总windows进程通信(三)
  • 《2024工业控制系统网络安全态势白皮书》
  • 2016年下半年试题二:论软件设计模式及其应用
  • 银川市长信箱被指“已读乱回”,官方通报:对相关责任人问责处理
  • 哈马斯官员:只要以军持续占领,哈马斯就不会放下武器
  • 艺术与医学的对话,瑞金医院办了一个展览
  • 上海论坛2025年会聚焦创新的时代,9份复旦智库报告亮相
  • 习近平在中共中央政治局第二十次集体学习时强调,坚持自立自强,突出应用导向,推动人工智能健康有序发展
  • 11-13世纪的地中海贸易