【面试篇】多线程

news2025/4/5 20:18:19

基础概念

线程的生命周期有哪些状态?它们是如何转换的?
答案:线程的生命周期有以下六种状态:
新建(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对库存更新操作进行加锁,确保在同一时刻只有一个线程能够更新库存。在更新库存的方法中,先获取锁,然后进行库存更新操作,最后释放锁。
同时,为了提高性能,对库存更新的逻辑进行了优化,将一些不必要的操作放在锁外面执行,只在锁内部执行关键的库存更新代码,减少锁的持有时间。
另外,添加了日志记录功能,对每次库存更新操作进行详细的日志记录,以便在出现问题时能够快速定位和排查。
通过以上措施,解决了库存数据不一致的问题,保证了多线程环境下库存更新的准确性和稳定性。

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

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

相关文章

计算机视觉算法实战——基于YOLOv8的自动驾驶障碍物实时感知系统

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​ ​​​​​​​​​ ​​ 引言:自动驾驶感知系统的关键挑战 自动驾驶技术正以前所未有的速度重塑交通出行方式&#xff…

【boost搜索引擎】下

boost搜索引擎 1. 编写搜索引擎模块 Searcher2. 编写 http_server 模块3. 编写前端模块4. 添加日志5. 补充 去掉暂停词6. 项目扩展方向 1. 编写搜索引擎模块 Searcher 这一模块主要提供建立索引,以及收到用户的发起的http请求通过Get方法提交的搜索关键字&#xff…

数据结构优化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 进程间通信…

第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(部分题解)

文章目录 前言日期统计题意: 冶炼金属题意: 岛屿个数题意: 子串简写题意: 整数删除题意: 总结 前言 一年一度的🏀杯马上就要开始了,为了取得更好的成绩,好名字写了下前年2023年蓝桥…

分析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]"解决依赖冲突 如果遇到依赖冲突,可使用以下命令安装,不…

如何在 Unity3D 导入 Spine 动画

一、前言 《如何在 Unity3D 项目中导入 Spine 动画》,虽然在网上有很多这种文章,直接将问题交给 DeepSeek 也能得到具体的操作流程,但是照着他们提供的方法还是能遇到几个问题,比如: AI 回答没有提到 Unity 无法识别.…

论文笔记:ASTTN模型

研究现状 现有研究大多通过分别考虑空间相关性和时间相关性或在滑动时间窗口内对这种时空相关性进行建模,而未能对直接的时空相关性进行建模。受最近图领域Transformer成功的启发,该模型提出利用局部多头自关注,在自适应时空图上直接建立跨时…

2025-4-2 蓝桥杯刷题情况(分布式队列)

1.题目描述 小蓝最近学习了一种神奇的队列:分布式队列。简单来说,分布式队列包含 N 个节点(编号为0至N-1,其中0号为主节点),其中只有一个主节点,其余为副节点。 主/副节点中都各自维护着一个队列,当往分布式队列中添加…

【Java中级】10章、内部类、局部内部类、匿名内部类、成员内部类、静态内部类的基本语法和细节讲解配套例题巩固理解【5】

❤️ 【内部类】干货满满,本章内容有点难理解,需要明白类的实例化,学完本篇文章你会对内部类有个清晰的认知 💕 内容涉及内部类的介绍、局部内部类、匿名内部类(重点)、成员内部类、静态内部类 🌈 跟着B站一位老师学习…

swift-7-汇编分析闭包本质

一、汇编分析 fn1里面存放的东西 func testClosure2() {class Person {var age: Int 10}typealias Fn (Int) -> Intvar num 0func plus(_ i: Int) -> Int {num ireturn num}return plus} // 返回的plus和num形成了闭包var fn1 getFn()print(fn1(1)) // 1print(fn1(…

Linux: 进程信号初识

目录 一 前言 二 信号的感性认识 三 信号处理常见方式 四 系统信号列表 五 信号的保存 六 信号的产生 1. 通过终端按键产生信号 2. 通过系统调用向进程发送信号 3. 硬件异常产生信号 4. 软件条件产生信号 一 前言 在Linux操作系统中,进程信号是一个非常重…

CSS--解决float: right在空间不够时会自动往下移的问题

原文网址:CSS--解决float: right在空间不够时会自动往下移的问题-CSDN博客 简介 众所周知,float: right在空间不够时会自动往下移。那么怎样让它不要往下移呢?本文介绍解决方案。 需求 我想写一个无需列表,每个列表后边跟一个…

深度学习 Deep Learning 第14章 自编码器

深度学习 Deep Learning 第14章 自编码器 内容概要 本章深入探讨了自编码器(Autoencoders),这是一种用于特征学习和降维的神经网络架构。自编码器通过编码器和解码器两个部分,将输入数据映射到一个内部表示(编码&…

C++(匿名函数+继承+多态)

#include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector> #include <memory>using namespace std;// 基类 Weapon class Weapon { protected:int atk; public:Weapon…