【面试篇】多线程

基础概念

线程的生命周期有哪些状态?它们是如何转换的?
答案:线程的生命周期有以下六种状态:
新建(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.mzph.cn/pingmian/75299.shtml

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

相关文章

unity运行中场景指定模型回放功能(模型是地形并且可以加载预制体进行回放)

回放和加载脚本 using System.Collections.Generic; using UnityEngine;public class TerrainRecorder : MonoBehaviour {[Header("基本设置")]public Terrain targetTerrain;public bool isRecording false;public bool isPlayingBack false;[Range(0.02f, 1f)] …

基于SpringBoot的河道水情大数据可视化分析平台设计与实现(源码+论文+部署讲解等)

需要资料,请文末联系 一、平台介绍 水情监测数据大屏 - 平台首页 日均水位 日均水速 二、论文内容 摘要(中文) 本文针对河道水情监测领域的数据管理和可视化分析需求,设计并实现了一套河道水情大数据可视化分析平台。该平台基…

Knife4j文档请求异常 空指针

打开swagger文档报空指针异常 java.lang.NullPointerException: nullat springfox.documentation.oas.mappers.SchemaMapper.model(SchemaMapper.java:97)at springfox.documentation.oas.mappers.SchemaMapper.mapModel(SchemaMapper.java:85)at springfox.documentation.oas…

车辆选择解决方案

车辆选择解决方案 /* * Purpose: 添加车辆选择的功能 -> 用户在选择不同的车辆时,重新初始化系统状态,清除之前的定时器,并根据新选择的车辆设置新的定时器,以实现对新车辆状态的实时加载。 * File Name: 车辆选择解决方案 * …

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

程序化广告行业(51/89):Cookie映射与移动设备ID映射解析

程序化广告行业(51/89):Cookie映射与移动设备ID映射解析 在当今数字化营销的浪潮中,程序化广告已经成为企业精准触达目标客户的重要手段。作为一名对程序化广告充满兴趣的学习者,我希望通过这篇博客和大家一起深入探索…

内网服务器centos7安装jdk17

1. 下载 JDK 17 安装包(在外网环境操作) 在可联网的机器上下载 JDK 17 的压缩包(推荐使用 OpenJDK): OpenJDK 官方源: Adoptium Eclipse Temurin Azul Zulu 直接下载命令示例(在外网机器上执行…

【学Rust写CAD】21 2D 点(point.rs)

源码 //matrix/point.rs use std::ops::Mul; use super::algebraic_units::{Zero, One}; use super::generic::Matrix;/// 点坐标结构体 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Point<X, Y>(Matrix<X, Y, One, Zero, Zero, One>);impl<X, Y>…

《AI大模型应知应会100篇》第7篇:Prompt Engineering基础:如何与大模型有效沟通

第7篇&#xff1a;Prompt Engineering基础&#xff1a;如何与大模型有效沟通 摘要 Prompt Engineering&#xff08;提示工程&#xff09;是与大模型高效沟通的关键技能。通过精心设计的Prompt&#xff0c;可以让模型生成更准确、更有用的结果。本文将从基础知识到高级策略&…

Java高频面试题1:Java SE

一、Java概述 1. Java语言的特点&#xff1f; 面向对象&#xff1a;封装、继承、多态。跨平台&#xff1a;通过JVM实现“一次编写&#xff0c;到处运行”。内存管理&#xff1a;自动垃圾回收&#xff08;GC&#xff09;&#xff0c;避免手动内存管理。多线程&#xff1a;内置…

基于RapidIO接口的DSP+GPU工业AI实时计算解决方案

基于RapidIO接口的DSPGPU工业AI实时计算解决方案是一种面向高性能、低延迟工业应用的异构计算架构&#xff0c;适用于工业自动化、机器视觉、预测性维护、机器人控制等场景。以下是该方案的核心设计思路和技术要点&#xff1a; 1. 方案背景与目标 工业需求&#xff1a; 工业…

SQL DB 数据类型

SQL DB 数据类型 引言 在数据库管理系统中,数据类型是定义和存储数据的方式。SQL(结构化查询语言)数据库中的数据类型决定了数据的存储格式、大小、取值范围以及如何处理数据。合理选择和使用数据类型对于确保数据库性能、数据完整性和应用程序的准确性至关重要。 SQL 数…

常见电源模块设计

目录 1. 5V电源模块 2. 3.3V电源模块 3. 1.9V电源模块 4. 220V转12V电源模块 1. 5V电源模块 参考电路 电路说明&#xff1a; 这个电路采用的是稳压芯片78L05&#xff0c;我是用的12V的电源模块转成为5V,为后续的供电。 2. 3.3V电源模块 参考电路&#xff1a; 电路说明…

python操作es

1、常用操作 ### 创建索引 bash curl -u elastic:123 -X PUT -H "Content-Type: application/json" -d mapping.json "http://0.0.0.0:9200/ai_kg_extraction_new_lower_tag_index" ### 删除索引 bash curl -u elastic:123 -X DELETE "http://0.0…

记一个.NET AOT交叉编译时的坑

记一个.NET AOT交叉编译时的坑 背景&#xff1a; 使用.NET9开发的Avalonia项目需要部署到Linux-arm64 踩坑&#xff1a; 根据官方AOT交叉编译文档配置后执行打包 dotnet publish -r linux-arm64提示error : The PrivateSdkAssemblies ItemGroup is required for _ComputeA…

【Linux篇】探索进程地址空间:计算机背后的虚拟世界

进程地址空间的奥秘&#xff1a;让你理解程序如何在计算机中生存 一. 程序地址空间1.1 基本概念1.2 虚拟内存管理1.3 为什么存在虚拟地址空间1.3.1 意义 2. 最后 本文将介绍进程地址空间的基本概念与结构&#xff0c;帮助读者理解操作系统如何管理和分配内存。进程地址空间指的…

17查询文档的方式

目录 1.鼠标放在你要查询的地方或者选中&#xff0c;按FnF1 2Assistant文档 3帮助菜单界面 1.鼠标放在你要查询的地方或者选中&#xff0c;按FnF1 2Assistant文档 3帮助菜单界面 大家一定要有 查询文档 的意识!! 未来实际开发中,一定会用到很多的第三方库和框架的. 很可能用到的…

壹起航:引领中国工厂迈向全球市场的先锋

在全球化的浪潮中&#xff0c;中国工厂正积极寻求拓展海外市场的新机遇。面对激烈的国际竞争&#xff0c;如何脱颖而出&#xff0c;成为行业翘楚&#xff1f;壹起航凭借其深厚的行业积淀和创新的营销理念&#xff0c;为中国工厂提供了全方位的出海解决方案。 一、构建国际化外…

“数据导航仪”:企业迁移知识库如何赋能精准决策

在全球化与区域经济一体化的浪潮下&#xff0c;企业迁移已成为经济发展的重要现象。 无论是为了拓展市场、降低成本&#xff0c;还是为了寻找更好的政策环境&#xff0c;企业迁移都牵动着无数从业者的心。 然而&#xff0c;面对海量且分散的企业迁移信息&#xff0c;金融机构…

理解激活函数,多个网络层之间如何连接

1. 激活函数如何在两个层之间作用 如果不在两个层之间添加激活函数&#xff0c;模型将无法学习非线性关系&#xff0c;表现出像线性模型一样的局限性。 LeakyReLU(0.2) 是一个激活函数&#xff0c;它的作用是对每一层的输出进行非线性转换。激活函数通常在神经网络中用于增加网…