面试总结之并发编程

news2025/1/12 12:01:05

一、ThreadLocal

1、什么是ThreadLocal

  • ThreadLocal是一种多线程隔离机制,提供了多线程环境下对共享变量访问的安全性
    在这里插入图片描述
  • 在多线程访问共享变量的场景中(如上图),一般的解决方案是对共享变量加锁,从而保证同一时刻只有一个线程能对共享变量进行更新(如下图),并且基于Happens-Before原则中的监视器锁规则,又保证了数据修改后对其他线程的可见性
    在这里插入图片描述
  • 加锁会带来性能的下降,ThreadLocal采取了一种空间换时间的思路,在每个线程中都用容器来存储共享变量的副本每个线程只对自己的变量副本进行访问和操作,如此,既解决了线程安全问题,又避免多线程竞争锁的开销
  • ThreadLcoal实现原理:Thread类中有成员变量ThreadLcoalMap,用来专门存储当前线程共享变量的副本,后续当前线程对共享变量的操作,都基于ThreadLcoalMap来进行,不会影响全局共享变量的值
    在这里插入图片描述

2、ThreadLocal在项目中的实际应用

  • 在典型的MVC系统架构中,登录后的用户每次访问接口,都会在请求头中携带一个Token,在控制层可以根据该Token解析出登录用户的基本信息,那如果要在服务层和持久层都要用到登录用户的信息,如RPC调用,更新用户信息等,那要该如何实现?
  • 这时就可以使用ThreadLcoal,在控制层拦截请求,将用户信息存储到ThreadLocal,如此,就可以在服务层和持久层获取到ThreadLcoal中存储的登录用户信息
    在这里插入图片描述
public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}
  • 使用:
    // 获取当前的登录用户id
    Long userId = UserHolder.getUser().getId();

扩展:

  • 其他场景的cookie、session等数据隔离的操作都可以通过ThreadLcoal实现
  • 数据库连接池中的connection连接交给ThreadLocal来管理,保证当前线程操作的都是同一个connection

3、ThreadLocal实现原理

  • 每个线程都有一个成员变量ThreadLocalMap,当线程访问ThreadLocal修饰的共享数据时,该线程就会在自己的ThreadLocalMap中存储一份共享数据的副本,key指向ThreadLocal这个弱引用,value保存的是共享数据的副本,因为每个线程都有一份共享数据的副本,以此就解决了线程安全问题
    在这里插入图片描述

  • ThreadLocal的set方法:

    public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap 
        ThreadLocalMap map = getMap(t);
        // 将当前元素存入ThreadLocalMap 
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

     // ....
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    // ...
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

  • ThreadLocal实现的关键在于ThreadLocalMap,Thread类中定义了ThreadLocal.ThreadLocalMap类型的成员变量threadLocals
ThreadLocal.ThreadLocalMap threadLocals = null;
  • map本质上是一个个<key,value>键值对形式的节点组成的数组,那ThreadLcoalMap的节点是什么样的呢
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            // 节点的构造方法
            Entry(ThreadLocal<?> k, Object v) {
                // key赋值
                super(k);
                // value赋值
                value = v;
            }
        }
  • 这里Entry节点中的key可以看作是ThreadLocal的弱引用,value为向ThreadLocal中存储的值,Entry的key继承了WeakReference
    在这里插入图片描述

小结:
实现ThreadLocal的关键点:

  • Thread类中有ThreadLocal.ThreadLocalMap类型的实例变量,每个线程都有自己的ThreadLocalMap,ThreadLocalMap内部维护着Entry数组,每个Entry都代表一个完整的对象,key是ThreadLocal的弱引用,value是ThreadLocal的泛型值
  • ThreadLocal本身不存储key,只是作为key来让线程往ThreadLocalMap中存取值
  • 每个线程在往ThreadLocal中设置值时,都是往线程自己的ThreadLocalMap中存值,取值也是以某个ThreadLocal类型的key作为引用,在线程自己的map中查找对应的key,以此来实现线程隔离

4、ThreadLocal内存泄露

  • 在JVM中,栈内存线程私有,存储对象的引用,堆内存线程共享,用来存储对象实例 ==》 栈存储了ThreadLocal、Thread的引用,堆存储了ThreadLocal和Thread对象的具体实例
    在这里插入图片描述
  • 当JVM发生GC后,会断开Entry中的key到ThreadLocal对象中的引用(key为弱引用),key为null,value为强引用不会为null,整个Entry不会为null,会依然在ThreadLocalMap中占据内存,当通过ThreadLocal的get方法获取数据时,ThreadLocal并不为null,但也无法通过为null的key去访问到该Entry的value,如此就会造成内存泄露(占据内存也无法访问到)
如果key为强引用是否会造成内存泄露

可以先看如下代码:

    ThreadLocal threadLocal = new ThreadLocal();
    threadLocal.set(new Object());
    threadLocal = null;
  • 在set方法执行完后,直接将threadLocal设为null,此时栈中Thread的引用到堆中ThreadLocal对象的指向断开了,但是Entry中的key到ThreadLocal的引用依然存在,GC依旧无法回收,同样会造成内存泄露
  • key为弱引用比强引用好在哪:
    • 同样是如上代码,当key为弱引用,threadLocal设为null时,栈中ThreadLocal Reference到堆中ThreadLocal的指向断开,Entry到threadLocal的指向也会断开,此时threadLocal就会被回收
    • ThreadLocal也会根据key.get() == null来判断key是否被回收,ThreadLocal可自行清理这些过期的key来避免内存泄露

5、父子线程如何共享数据

  • 父线程不能用ThreadLocal来给子线程传值,父子线程之间的数据共享需要通过InheritableThreadLocal来实现,即在主线程的InheritableThreadLocal实例设置值,在子线程中就可以获取到设置的值
       InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
//        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        // 向主线程中的threadLocal设值
        threadLocal.set("世界上最好的编程语言");

        // 子线程
        Thread sonThread = new Thread(){
            @Override
            public void run() {
//                super.run();
                System.out.println(threadLocal.get() + " 是Java");
            }
        };
        sonThread.start();

在Thread类中,有 ThreadLocal.ThreadLocalMap类型的成员变量threadLocals和inheritableThreadLocals:

 ThreadLocal.ThreadLocalMap threadLocals = null;
 // ...
 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

在Thread的init()方法中,如果父线程的 inheritableThreadLocals 不为空,就把它赋给当前线程(子线程)的 inheritableThreadLocals

 if (inheritThreadLocals && parent.inheritableThreadLocals != null)
     this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
     this.stackSize = stackSize;

     /* Set thread ID */
     tid = nextThreadID();
     
     // ....
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

扩展、Java四种对象引用

  • 强引用:程序代码中普通存在的赋值行为,如:Object obj = new Object(); 只要强引用关系还在,对象就永远不会被回收
  • 软引用:不是必须存活的对象,JVM会在内存溢出之前进行回收(即内存满了才会进行回收),如:缓存
  • 弱引用:引用关系比软引用还弱,不管JVM内存是否够用,都会回收对象占用的内存
  • 虚引用:又称为"幽灵引用"、“幻影引用”,是最弱的引用关系,完全不会影响对象的回收,唯一的作用是对象被回收时收到一个系统通知

二、Java内存模型

三、锁

1、synchronized

synchronized可以用来修饰实例方法、静态方法、代码块,以保证程序代码的原子性

  • synchronized修饰实例方法:进入同步代码前要获得当前对象实例的锁
synchronized void method(){
    // ...
 }
  • synchronized修饰静态方法:给当前类加锁,作用于类的所有对象实例,进入同步代码前要先获得class的锁,因为静态成员不属于任何一个实例对象,属于类成员(static声明这是该类的静态资源,不管new了多少个对象,只有一份)
    如果线程A调用某实例对象的非静态同步方法,而线程B调用该实例对象所属类的静态同步方法,这种情况会被允许,不会发生互斥现象,因为访问静态同步方法占用的锁是当前类的锁,而访问非静态同步方法占用的锁是当前实例对象的锁
synchronized static void method() {
   // ...
}
  • synchronized修饰代码块:指定加锁对象,对给定的类/ 对象加锁,synchronized(this) 或synchronized(object) 表示进入同步代码块前,要先获得给定对象的锁,synchronized(类名.class)表示进入同步代码块前要获得当前class的锁
synchronized (Person.class) {
  // ...
}

2、synchronized的实现原理

  • 当我们使用synchronized时,JVM会自动进行lock和unlock操作

  • synchronized修饰代码块时,JVM采用monitorenter、monitorexit两个指令来实现同步,monitorenter指向同步代码块的开始位置(lock操作),第一个monitorexit指向同步代码块的结束位置(unlock操作),第二个monitorexit保证出现异常也能unlock
    在这里插入图片描述

  • synchronized修饰代码块时,采用ACC_SYNCHRONIZED来标识该方法是一个synchronized修饰的同步方法
    在这里插入图片描述

  • monitorenter、monitorexit和ACC_SYNCHRONIZED都是基于Monitor对象实现的

  • 实例对象结构中有对象头,对象头中MarkWord指针会指向Monitor,Monitor是一种同步工具 / 同步机制,在Java虚拟机(Hotspot)中,Monitor由ObjectMonitor实现,又称为内部锁,或者Monitor锁

  • Monitor的工作原理:

    • ObjectMonitor有两个队列:WaitSet、EntryList,用来保存ObjectWaiter对象列表
    • _owner:获取Monitor对象的线程进入_owner区时,_count+1;如果线程调用了wait()方法,就会释放Monitor对象,_owner为空,_count-1,同时该等待线程进入_WaitSet中,等待被唤醒
ObjectMonitor() {
 _header = NULL ;
 _count = 0 ; // 记录 线程获取锁的次数
 _waiters = 0 ,
 _recursions = 0 ; / /锁 的 重 ⼊ 次 数
 _object = NULL ;
 _owner = NULL ; // 指向持 有ObjectMonitor对 象 的线程
 _WaitSet = NULL ; // 处 于wait状 态 的线程,会被 加 ⼊ 到_WaitSet
 _WaitSetLock = 0 ;
 _Responsible = NULL ;
 _succ = NULL ;
  _cxq = NULL ;
 FreeNext = NULL ;
 _EntryList = NULL ; // 处 于 等 待 锁block状 态 的线程,会被 加 ⼊ 到 该 列 表
 _SpinFreq = 0 ;
 _SpinClock = 0 ;
 OwnerIsThread = 0 ;
 }

去医院就诊的过程就和Monitor比较类似:

  • 门诊大厅(EntrySet):所有待进入的线程都必须先在EntrySet挂号才有资格就诊
  • 就诊室(_owner):_owner中只能有一个线程就诊,就诊完毕线程就自行离开
  • 候诊室(WaitSet):就诊室繁忙时,进入等待区(WaitSet),就诊室空闲时就从等待区(WaitSet)叫醒等待就诊的线程
    在这里插入图片描述
    小结:
  • monitorenter:在判断拥有同步表示ACC_SYNCHRONIZED后,抢先进入该同步方法的线程会优先拥有Monitor的owner,此时计数器+1
  • monitorexit:当执行完退出后,计数器-1,计数器归零后被其他进入的线程获取
  • 基于Monitor中的计数器,Monitor可以记录锁重入的次数(线程获取锁的次数)

未完待续…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1099732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“权限之舞:Linux安全之道”

W...Y的主页&#x1f60a; 代码仓库分享&#x1f495; &#x1f354;前言&#xff1a; 在之前的Linux博客中&#xff0c;我们学习了基础的Linux指令&#xff0c;具体可以订阅一下博主的Linux专栏学习。当我们想进行递归删除文件时等等许多操作中&#xff0c;只有在root账号中…

IPV6 ND协议--源码解析【根源分析】

ND协议介绍 ND介绍请阅读上一篇文章&#xff1a;IPv6知识 - ND协议【一文通透】11.NDP协议分析与实践_router solicitation报文中不携带source link-layer address-CSDN博客 ND协议定义了5种ICMPv6报文类型&#xff0c;如下表所示&#xff1a; NS/NA报文主要用于地址解析RS/…

使用PyTorch解决多分类问题:构建、训练和评估深度学习模型

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

【学习笔记】RabbitMQ02:交换机,以及结合springboot快速开始

参考资料 RabbitMQ官方网站RabbitMQ官方文档噼咔噼咔-动力节点教程 文章目录 四、RabbitMQ &#xff1a;Exchange 交换机4.1 交换机类型4.2 扇形交换机 Fanout Exchange4.2.1 概念4.2.1 实例&#xff1a;生产者4.2.1.1 添加起步依赖4.2.1.2 配置文件4.2.1.3 JavaBean进行配置4.…

NGF ; -R : Trk NTRK

NTRK基因融合的机制与靶向治疗 - 知乎 【NTRK基因】共识已发布&#xff0c;24款获证&#xff0c;2款NGS产品已布局

开关电源测试解决方案之浪涌电流测试 -纳米软件

浪涌电流是由于开关、断电等引起的突发电流&#xff0c;这种电流会直接影响到设备的寿命和可靠性&#xff0c;因此浪涌电流测试是开关电源测试的一个重要项目。本文将会介绍浪涌电流测试要求以及测试方法。 什么是浪涌电流? 浪涌电流是指电源接通瞬间&#xff0c;流入电源设备…

Python中兔子递归函数的例子

本文将详细介绍Python中兔子递归函数的例子&#xff0c;展示递归函数的基本实现方法及其原理。 一、递归函数的概念 递归函数是指在函数内部调用自身的函数。通过递归函数&#xff0c;可以将复杂问题分解成简单的子问题来解决。 这种过程是有限的&#xff0c;当子问题足够小…

分布式存储系统——ceph

目录 一、分布式存储类型 1、块存储 2、文件存储 3、对象存储 二、ceph概述 1、ceph简介 2、ceph的优势 3、ceph架构 1&#xff09;RADOS 基础存储系统&#xff08;Reliab1e&#xff0c;Autonomic&#xff0c;Distributed object store 2&#xff09;LIBRADOS 基础库 …

Linux 下I/O操作

一、文件IO 文件 IO 是 Linux 系统提供的接口&#xff0c;针对文件和磁盘进行操作&#xff0c;不带缓存机制&#xff1b;标准IO是C 语言函数库里的标准 I/O 模型&#xff0c;在 stdio.h 中定义&#xff0c;通过缓冲区操作文件&#xff0c;带缓存机制。   标准 IO 和文件 IO 常…

论文阅读 Memory Enhanced Global-Local Aggregation for Video Object Detection

Memory Enhanced Global-Local Aggregation for Video Object Detection Abstract 人类如何识别视频中的物体&#xff1f;由于单一帧的质量低下&#xff0c;仅仅利用一帧图像内的信息可能很难让人们在这一帧中识别被遮挡的物体。我们认为人们识别视频中的物体有两个重要线索&…

怎么使用动态代理IP提升网络安全,动态代理IP有哪些好处呢

随着互联网的普及和数字化时代的到来&#xff0c;网络安全问题越来越受到人们的关注。动态代理IP作为网络安全中的一种技术手段&#xff0c;被越来越多的人所采用。本文将介绍动态代理IP的概念、优势以及如何应用它来提升网络安全。 一、动态代理IP的概念 动态代理IP是指使用代…

nginx.4——正向代理和反向代理(七层代理和四层代理)

1、正向代理反向代理 nginx当中有两种代理方式 七层代理(http协议) 四层代理(tcp/udp流量转发) 七层代理 七层代理&#xff1a;代理的是http的请求和响应。 客户端请求代理服务器&#xff0c;由代理服务器转发给客户端http请求。转发到内部服务器&#xff08;可以单台&#…

Mac OS m1 下安装Gradle4.8.1

1. 下载、解压 1.1 下载地址 https://gradle.org 往下翻 或者选择 任何 你想要的版本 ,点击 binary-only 即可下载 . 1.2 解压到指定目录 2. 配置环境变量 2.1 编辑环境文件 vi ~/.bash_profile #GRADLE相关配置 GRADLE_HOME/Users/zxj/Documents/devSoft/gradle-4.8.1 e…

视频集中存储/视频监控管理平台EasyCVR如何免密登录系统?详细操作如下

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

【Mac】时间机器频繁提示磁盘没有正常推出

问题描述 有一次在进行时间机器备份的时候总是提示“磁盘没有正常推出”&#xff0c;并且好几次直接导致系统重启… 估计是 MacOS 系统 bug 解决 看了 Vex 一个帖子之后设置了一个硬盘是否休眠就好了&#xff0c;不要勾选让硬盘处于休眠就可以了&#xff0c;在电池选项界面中…

Sonar跨服务扫描Jenkins Job

需求&#xff1a;验收环境新增sonar扫描&#xff0c;(困难点&#xff1a;验收环境jenkins和开发环境sonar不通&#xff0c;验收环境没有办法重新安装Sonar&#xff0c;数据库等服务。 解决方法&#xff1a;验收Jenkins和开发环境jenkins做免密&#xff0c;通过修改验收环境jenk…

基于 nodejs+vue网上考勤系统

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

VSCode连接代理

VSCode连接代理 首先有代理 然后在设置里搜代理 然后再在windows的设置–>网络–>代理 拼接上就行 最后重启

问题解决:使用Mybatis Plus的Mapper插入达梦数据库报“数据溢出”错误

前言 使用Mybatis Plus的Mapper插入达梦数据库报“数据溢出”错误 文章目录 前言问题描述错误日志输出排查过程最终解决办法 问题描述 在进行批量插入中&#xff0c;抛出异常为数据溢出 插入方法&#xff1a;this.baseMapper.insertBatchSomeColumn()抛出异常&#xff1a;数据…

Hadoop分布式文件系统——HDFS

1.介绍 HDFS (Hadoop Distributed File System)是 Hadoop 下的分布式文件系统,具有高容错、高吞吐量等特性,可以部署在低成本的硬件上。 2.HDFS 设计原理 2.1 HDFS 架构 HDFS 遵循主/从架构,由单个 NameNode(NN) 和多个 DataNode(DN) 组成: