【面试篇】多线程
基础概念
线程的生命周期有哪些状态?它们是如何转换的?
答案:线程的生命周期有以下六种状态:
新建(New):线程被创建但尚未启动,此时线程对象已被分配内存空间,相关属性已被初始化。
就绪(Runnable):线程调用start()方法后进入此状态,表明线程已准备好运行,等待系统调度获取 CPU 资源。
运行(Running):线程获取到 CPU 资源,正在执行run()方法中的代码逻辑。
阻塞(Blocked):线程因某些原因暂停执行,放弃 CPU 使用权。如等待获取锁、执行wait()方法进入等待状态、执行 I/O 操作等。
超时等待(Timed Waiting):线程进入等待状态,但有指定的等待时间。例如通过Thread.sleep(long millis)、wait(long timeout)等方法进入此状态,时间到期后会自动返回就绪状态。
终止(Terminated):线程执行完run()方法中的代码,或者因异常等原因提前结束,线程进入终止状态,此时线程的生命周期结束。
状态转换:新建状态的线程调用start()方法进入就绪状态;就绪状态的线程被 CPU 调度选中进入运行状态;运行状态的线程执行wait()方法或等待获取锁等情况会进入阻塞状态,执行Thread.sleep(long millis)等方法会进入超时等待状态;阻塞状态和超时等待状态的线程在满足相应条件(如被notify()唤醒、获取到锁、等待时间结束等)后会回到就绪状态;运行状态的线程执行完run()方法或出现异常等会进入终止状态。
同步与锁
synchronized关键字的作用是什么?它是如何实现同步的?
答案:synchronized关键字用于实现线程之间的同步,确保在同一时刻只有一个线程能够访问被 synchronized修饰的代码块或方法,从而保证数据的一致性和完整性。
当一个线程访问被synchronized修饰的代码块或方法时,它会先获取对象的锁(如果是静态方法,则获取类的锁)。如果锁已经被其他线程持有,那么当前线程会进入阻塞状态,直到获取到锁。在获取到锁后,线程才能执行相应的代码。当线程执行完同步代码块或方法后,会释放锁,以便其他线程可以获取锁并执行同步代码。
说说ReentrantLock和synchronized的区别。
答案:
实现机制:synchronized是 Java 语言的关键字,由 JVM 底层实现;ReentrantLock是 Java.util.concurrent 包中的类,通过代码实现。
锁的获取与释放:synchronized在代码块或方法执行完后自动释放锁;ReentrantLock需要手动调用unlock()方法释放锁,通常在finally块中进行,以确保锁一定会被释放,否则可能导致死锁。
可重入性:两者都具有可重入性,即同一个线程可以多次获取同一个锁。
公平性:synchronized是非公平锁,线程获取锁的顺序不确定;ReentrantLock可以通过构造函数参数指定是否为公平锁,公平锁会按照线程请求锁的顺序分配锁,减少线程饥饿现象,但会降低一定的性能。
功能特性:ReentrantLock提供了更多的功能特性,如可以尝试获取锁(tryLock()方法)、可中断地获取锁(lockInterruptibly()方法)等,而synchronized不具备这些功能。
什么是死锁?如何避免死锁?
答案:死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的状态,若无外力作用,这些线程将永远无法继续执行。例如,线程 A 持有资源 1 并等待资源 2,而线程 B 持有资源 2 并等待资源 1,此时两个线程相互等待,形成死锁。
避免死锁的方法有:
按顺序获取锁:确保所有线程按照相同的顺序获取锁,避免锁的交叉获取。
避免锁嵌套:尽量减少在一个同步块中获取另一个锁的情况,降低死锁发生的可能性。
设置锁超时:使用具有超时机制的锁获取方法,如ReentrantLock的tryLock(long timeout, TimeUnit unit)方法,当获取锁超时后,线程可以放弃等待,避免无限期等待。
使用资源分配图检测:在程序运行过程中,通过资源分配图来检测是否存在死锁,如果发现死锁,可以采取相应的措施进行处理,如终止某些线程或释放某些资源。
线程间通信
线程间如何进行通信?请列举几种方式。
答案:
使用wait()、notify()和notifyAll()方法:这三个方法是Object类的方法,在同步代码块或同步方法中使用。一个线程调用wait()方法后会释放锁并进入等待状态,其他线程可以调用notify()或notifyAll()方法唤醒等待的线程。notify()方法随机唤醒一个等待的线程,notifyAll()方法唤醒所有等待的线程。
使用BlockingQueue:BlockingQueue是一个阻塞队列,当队列满时,向队列中添加元素的线程会被阻塞;当队列为空时,从队列中获取元素的线程会被阻塞。通过这种方式实现线程间的通信和同步,例如ArrayBlockingQueue、LinkedBlockingQueue等。
使用CountDownLatch:CountDownLatch可以让一个或多个线程等待其他线程完成一组操作后再继续执行。通过countDown()方法减少计数器的值,当计数器的值为 0 时,等待的线程被唤醒继续执行。
使用CyclicBarrier:CyclicBarrier用于让一组线程互相等待,直到所有线程都到达某个屏障点,然后再一起继续执行。它可以重复使用,当所有线程都到达屏障后,屏障会被重置,可以再次使用。
解释一下wait()和sleep()方法的区别。
答案:
所属类:wait()方法是Object类的方法,而sleep()方法是Thread类的方法。
释放锁的行为:wait()方法会释放当前线程持有的对象锁,使得其他线程可以获取该锁并访问同步代码块或方法;sleep()方法不会释放锁,线程在睡眠期间仍然持有锁,其他线程无法访问被该线程锁住的资源。
使用场景:wait()方法通常用于线程间的通信和协作,例如一个线程等待另一个线程完成某个操作后再继续执行;sleep()方法主要用于让线程暂停一段时间,例如在循环中控制执行频率,或者在某些操作之间添加延迟。
唤醒方式:wait()方法需要被其他线程调用notify()或notifyAll()方法唤醒,或者等待指定的时间后自动唤醒;sleep()方法在指定的睡眠时间到达后自动唤醒。
并发容器与框架
请介绍一下ConcurrentHashMap的实现原理。
答案:ConcurrentHashMap是 Java 中用于在多线程环境下高效存储和访问数据的哈希表实现。它采用了分段锁(Segment)的技术,将整个哈希表分成多个段,每个段都有自己的锁。在 JDK 8 及以后的版本中,ConcurrentHashMap摒弃了分段锁的概念,采用了 CAS(Compare and Swap)操作和synchronized关键字来实现并发安全。
当进行插入、删除或查询操作时,ConcurrentHashMap首先根据键的哈希值确定要操作的桶(bucket)。对于插入操作,会使用 CAS 操作尝试将新元素插入到桶中,如果桶为空,则直接插入;如果桶不为空,则可能需要对桶中的元素进行遍历和更新。在遍历和更新过程中,会使用synchronized关键字对桶进行加锁,以确保同一时刻只有一个线程能够访问该桶。对于查询操作,由于ConcurrentHashMap的桶中的元素是通过链表或红黑树来存储的,所以查询操作可以在不加锁的情况下进行,通过 volatile 关键字保证了桶中元素的可见性,从而实现了高并发下的高效查询。
Java 中的BlockingQueue有哪些实现类?它们的特点是什么?
答案:
ArrayBlockingQueue:基于数组实现的有界阻塞队列,在创建时需要指定队列的容量。它按照先进先出(FIFO)的原则对元素进行排序,插入和删除操作在队列的两端进行,使用一把锁来保证并发安全。
LinkedBlockingQueue:基于链表实现的阻塞队列,可以指定队列的容量,也可以不指定,默认容量为Integer.MAX_VALUE。它同样按照 FIFO 原则对元素进行排序,插入和删除操作分别在链表的头和尾进行,使用两把锁(一把用于读操作,一把用于写操作)来提高并发性能。
PriorityBlockingQueue:基于优先级堆实现的无界阻塞队列,元素按照优先级进行排序。在插入元素时,会根据元素的优先级将其插入到合适的位置,取出元素时,会取出优先级最高的元素。它使用一把锁来保证并发安全。
DelayQueue:基于优先级队列实现的无界阻塞队列,队列中的元素必须实现Delayed接口,该接口定义了getDelay(TimeUnit unit)方法用于获取元素的延迟时间。只有当元素的延迟时间到期后,才能从队列中取出元素。它使用一把锁和一个条件变量来实现延迟队列的功能。
谈谈你对Executor框架的理解。它有哪些主要的组件?
答案:Executor框架是 Java 中用于管理和执行线程任务的框架,它提供了一种高效的方式来创建、执行和管理线程,将任务的提交与执行解耦,使得代码更加易于维护和扩展。
主要组件包括:
Executor接口:定义了一个execute(Runnable command)方法,用于执行给定的Runnable任务。
ExecutorService接口:继承自Executor接口,提供了更丰富的方法,如提交任务、关闭线程池等。它可以管理一组线程,并且可以通过不同的策略来分配任务给线程执行。
ThreadPoolExecutor类:ExecutorService接口的主要实现类,用于创建线程池。它可以根据不同的参数配置创建不同类型的线程池,如固定大小的线程池、可缓存的线程池等。通过线程池可以有效地复用线程,减少线程创建和销毁的开销,提高系统的性能和响应性。
ScheduledExecutorService接口:用于定时执行任务或周期性执行任务的接口,继承自ExecutorService接口。
ScheduledThreadPoolExecutor类:ScheduledExecutorService接口的实现类,用于创建定时线程池,可以按照指定的延迟时间或周期执行任务。
性能优化与实践
在多线程编程中,如何提高程序的性能?
答案:
合理使用线程池:线程池可以复用线程,减少线程创建和销毁的开销。根据任务的特点选择合适的线程池类型,如固定大小的线程池适用于任务数量相对稳定的情况,可缓存的线程池适用于任务数量波动较大的情况。
减少锁竞争:锁的竞争会导致线程阻塞和上下文切换,降低程序性能。可以通过优化锁的粒度,尽量缩小同步代码块的范围,或者使用无锁的数据结构和算法来避免锁竞争。
避免线程上下文切换:线程上下文切换会消耗一定的时间和资源。可以通过减少线程的数量、合理安排任务的执行顺序、避免不必要的阻塞等方式来减少线程上下文切换的发生。
使用无锁数据结构:在一些场景下,无锁数据结构可以提供更高的并发性能,如ConcurrentLinkedQueue、AtomicInteger等。这些数据结构通过使用 CAS 操作等技术来实现无锁并发访问,避免了锁的开销。
优化线程间通信:合理使用线程间通信机制,如BlockingQueue、CountDownLatch等,避免不必要的等待和唤醒操作,提高线程间的协作效率。
充分利用多核处理器:根据系统的处理器核心数量,合理分配线程数量,使得每个核心都能充分发挥作用,提高系统的并行度。
请描述一个你在实际项目中遇到的多线程问题,以及你是如何解决的。
答案:(以下是一个示例,你可以根据实际情况进行修改和补充)在一个电商项目中,有多个线程同时对商品库存进行更新操作。由于并发访问,出现了库存数据不一致的问题,有些订单扣除了库存但没有更新到数据库,而有些订单则更新了多次库存,导致库存数量不准确。
解决方法如下:
首先,分析问题的原因是多个线程对库存的并发更新没有进行有效的同步控制。
然后,使用ReentrantLock对库存更新操作进行加锁,确保在同一时刻只有一个线程能够更新库存。在更新库存的方法中,先获取锁,然后进行库存更新操作,最后释放锁。
同时,为了提高性能,对库存更新的逻辑进行了优化,将一些不必要的操作放在锁外面执行,只在锁内部执行关键的库存更新代码,减少锁的持有时间。
另外,添加了日志记录功能,对每次库存更新操作进行详细的日志记录,以便在出现问题时能够快速定位和排查。
通过以上措施,解决了库存数据不一致的问题,保证了多线程环境下库存更新的准确性和稳定性。
相关文章:
【面试篇】多线程
基础概念 线程的生命周期有哪些状态?它们是如何转换的? 答案:线程的生命周期有以下六种状态: 新建(New):线程被创建但尚未启动,此时线程对象已被分配内存空间,相关属性已…...
MySQL表缺乏主键或唯一索引对主从复制的深度影响及解决方案
引言 在MySQL数据库设计中,主键(Primary Key)和唯一索引(Unique Index)不仅是数据完整性的基石,更是主从复制(Replication)可靠性的关键。然而,许多开发者或DBA因历史遗…...
计算机视觉算法实战——基于YOLOv8的自动驾驶障碍物实时感知系统
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 引言:自动驾驶感知系统的关键挑战 自动驾驶技术正以前所未有的速度重塑交通出行方式ÿ…...
【boost搜索引擎】下
boost搜索引擎 1. 编写搜索引擎模块 Searcher2. 编写 http_server 模块3. 编写前端模块4. 添加日志5. 补充 去掉暂停词6. 项目扩展方向 1. 编写搜索引擎模块 Searcher 这一模块主要提供建立索引,以及收到用户的发起的http请求通过Get方法提交的搜索关键字ÿ…...
数据结构优化DP总结
单调栈:Codeforces Round 622 (Div. 2) C2. Skyscrapers (hard version) 简单来讲就是最后需要呈现出一个单峰数组,使得总高度最高。 最开始想到暴力枚举每一个元素都充当最高的“单峰”,但是这里的 n 过大,这样枚举肯定会TLE。 …...
[Linux系统编程]进程间通信—system V
进程间通信—system V 1. System V 共享内存(Shared Memory)1.1 共享内存的建立过程1.2 共享内存函数2. System V 消息队列(Message Queues)3. System V 信号量(Semaphores)4. 总结前言: 之前所提的管道通信是基于文件的,OS没有做过多的设计工作。 system V 进程间通信…...
Eigen库几何模块深度解析与实践指南
Eigen库几何模块深度解析与实践指南 a. Eigen几何模块概述 i. 几何模块的核心功能 在三维空间中,几何变换是描述物体位置和姿态变化的基础,其数学基础涵盖了线性代数中的矩阵运算等知识。Eigen库的几何模块为这些变换提供了高效且便捷的实现方式。 旋转、平移和缩放是三维…...
第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(部分题解)
文章目录 前言日期统计题意: 冶炼金属题意: 岛屿个数题意: 子串简写题意: 整数删除题意: 总结 前言 一年一度的🏀杯马上就要开始了,为了取得更好的成绩,好名字写了下前年2023年蓝桥…...
C语言常见3种排序
主要是三种排序方法:冒泡排序、选择排序、插入排序。 文章目录 一、冒泡排序 1.代码: 2.工作原理: 3.具体过程: 二、选择排序 1.代码 2. 工作原理 3.具体过程: 三、插入排序 1.代码 2.工作原理 3.具体过程 总结 一、…...
分析sys高问题的方法总结
一、背景 sys高的问题往往属于底层同学更需要关注的问题,sys高的问题往往表现为几种情况,一种是瞬间的彪高,一种是持续的彪高。这篇博客里,我们总结一下常用的分析方法和分析工具的使用来排查这类sys高的问题。 二、通过mpstat配…...
智谱发布AI Agent“AutoGLM沉思”,开启AI“边想边干”新时代
近日,智谱正式推出全新AI Agent产品——AutoGLM沉思,标志着人工智能从“思考”迈向“执行”的关键突破。该智能体不仅具备深度研究能力,还能自主完成实际操作,真正实现“边想边干”的智能化应用。 在演示环节,智谱展示…...
使用Leaflet对的SpringBoot天地图路径规划可视化实践-以黄花机场到橘子洲景区为例
目录 前言 一、路径规划需求 1、需求背景 2、技术选型 3、功能简述 二、Leaflet前端可视化 1、内容布局 2、路线展示 3、转折路线展示 三、总结 前言 在当今数字化与智能化快速发展的时代,路径规划技术已经成为现代交通管理、旅游服务以及城市规划等领域的…...
【小兔鲜】day02 Pinia、项目起步、Layout
【小兔鲜】day02 Pinia、项目起步、Layout 1. Pinia2. 添加Pinia到Vue项目3. 案例:Pinia-counter基础使用3.1 Store 是什么?3.2 应该在什么时候使用 Store? 4. Pinia-getters和异步action4.1 getters4.2 action如何实现异步 1. Pinia Pinia 是 Vue 的专…...
PyTorch 激活函数
激活函数是神经网络中至关重要的组成部分,它们为网络引入了非线性特性,使得神经网络能够学习复杂模式。PyTorch 提供了多种常用的激活函数实现。 常用激活函数 1. ReLU (Rectified Linear Unit) 数学表达式: PyTorch实现: torch.nn.ReLU(inplaceFals…...
魔塔社区使用llamafactory微调AI阅卷试题系统
启动 LLaMA-Factory 1. 安装 LLaMA-Factory 执行安装指令 git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory pip install -e ".[torch,metrics]"解决依赖冲突 如果遇到依赖冲突,可使用以下命令安装,不…...
Java面试黄金宝典29
1. 什么是普通索引和唯一性索引 定义: 普通索引:是最基本的索引类型,它为数据表中的某一列或多列建立索引,以加快数据的查询速度。它不限制索引列的值重复,允许存在多个相同的值。唯一性索引:在普通索引的基…...
git `switch` 命令详解与实用示例
文章目录 git switch 命令详解与实用示例git switch vs git checkoutgit switch 用法1. 切换到已有分支2. 创建并切换到新分支3. 切换到上一个分支4. 切换到远程分支(自动创建本地分支并追踪远程)5. 放弃未提交的修改并切换分支 总结 git switch 命令详解…...
Oracle中文一二三四排序【失败】
原文地址: Oracle数据库如何对中文的一二三四五六七八九十数进行正序排列排序_中文数字排序-CSDN博客 自定义排序函数 -- 自定义中文映射阿拉伯数字函数 CREATE OR REPLACE FUNCTION P_ORDER_CHINESE_TO_ARABIC(V_NUM VARCHAR2) RETURN NUMBER IS BEGIN-- 根据…...
AWS S3 和 Lambda 使用
目录: AWS概述 EMR Serverless AWS VPC及其网络 关于AWS网络架构的思考 AWS S3 和 Lambda 使用 本文将通过一个实例来说明如何使用 AWS S3 和 Lambda。 使用场景:通过代码将文件上传到S3,该文件需要是公开访问的,并对上传的文件进…...
Mysql 在什么样的情况下会产生死锁?
在 MySQL 中,死锁是指两个或多个事务相互等待对方释放锁,导致所有相关事务无法继续执行的情况。死锁会影响数据库的并发性能,因此需要及时检测并处理。假设有两个事务 T1 和 T2: 事务 T1 首先锁定 表 A 的行 1。然后尝试锁定 表 B…...
符号秩检验
内容来源 非参数统计(第2版) 清华大学出版社 王星 褚挺进 编著 符号秩检验 在符号检验的基础上,增加了数据绝对值大小的信息 检验统计量 用一个简单的例子来说明 样本数据 X i , i 1 , ⋯ , 6 X_i,i1,\cdots,6 Xi,i1,⋯,6 如下 X …...
RainbowDash 的 Robot
H RainbowDash 的 Robot - 第七届校赛正式赛 —— 补题 题目大意: 给一个 n ∗ m n*m n∗m 的二维网格,在第 i i i 列中,前 a i a_i ai 单元格被阻断,无法通行,即 [ 1 , a i ] [1,a_i] [1,ai] 。 一个机器人正…...
yum repolist all全部禁用了 怎么办
文章目录 步骤思考解决yum仓库全部被禁用的问题步骤思考: 检查仓库状态:运行yum repolist all,查看所有仓库的启用状态。 被禁用的仓库会显示为disabled。 启用所有仓库:可以逐一启用,或者使用命令批量启用。 例如使用yum-config-manager --enable ‘*’,但需要注意是否有…...
SQL WHERE 与 HAVING
WHERE 和 HAVING 都是 SQL 中用于筛选数据的子句,但它们有重要的区别 WHERE 子句 在 分组前 过滤数据 作用于 原始数据行 不能使用聚合函数 执行效率通常比 HAVING 高 SELECT column1, column2 FROM table WHERE condition; HAVING 子句 在 分组后 过滤数据 …...
如何在 Unity3D 导入 Spine 动画
一、前言 《如何在 Unity3D 项目中导入 Spine 动画》,虽然在网上有很多这种文章,直接将问题交给 DeepSeek 也能得到具体的操作流程,但是照着他们提供的方法还是能遇到几个问题,比如: AI 回答没有提到 Unity 无法识别.…...
子网划分2
子网分配的问题,下列vlsm如何设置? 某公司申请了一个C类202.60.31.0的IP地址,要求设置三个子网,一个为100台主机,一个为50台主机,另一个为50台主机,用VLSM如何设置? 哪位高手指教一…...
C++的UDP连接解析域名地址错误
背景 使用c开发一个udp连接功能的脚本,可以接收发送数据,而且地址是经过内网穿透到外网的 经过 通常发送数据给目标地址,需要把目的地址结构化,要么使用inet_addr解析ip地址,要么使用inet_pton sockaddr_in target…...
23种设计模式中的观察者模式
定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。 观察者模式是一种发布-订阅模式。它让发送通知的一方(被观察者)和接收通知的一方(观察者)能够解耦…...
论文笔记:ASTTN模型
研究现状 现有研究大多通过分别考虑空间相关性和时间相关性或在滑动时间窗口内对这种时空相关性进行建模,而未能对直接的时空相关性进行建模。受最近图领域Transformer成功的启发,该模型提出利用局部多头自关注,在自适应时空图上直接建立跨时…...
Java单例模式详解
单例模式详解 一、单例模式概述 单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。 核心特点 唯一实例:保证一个类只有一个实例存在全局访问:提供统一的访问入…...
