【面试题系列】Java 多线程面试题深度解析

在这里插入图片描述

本文涉及Java 多线程面试题,从基础到高级,希望对你有所帮助!

一、基础概念类

1. 请简述 Java 中线程的几种状态及其转换条件

题目分析:这是多线程基础中的基础,考查对线程生命周期的理解,在多线程编程中,线程状态的转换是核心机制之一。
答案
Java 中线程有六种状态,定义在 Thread.State 枚举中:

  • NEW(新建):线程被创建但还未调用 start() 方法。例如:
Thread thread = new Thread(() -> System.out.println("Running")); 
// 此时 thread 处于 NEW 状态
  • RUNNABLE(可运行):线程调用 start() 方法后进入该状态,它可能正在运行,也可能在等待 CPU 时间片。
  • BLOCKED(阻塞):线程在等待获取一个排它锁(synchronized 同步块)时进入该状态,当锁被释放且该线程竞争到锁时,会转换回 RUNNABLE 状态。
public class BlockedExample {private static final Object lock = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (lock) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(() -> {synchronized (lock) {System.out.println("t2 got the lock");}});t1.start();t2.start();// t2 可能会进入 BLOCKED 状态等待 lock}
}
  • WAITING(等待):线程调用 Object.wait()Thread.join()LockSupport.park() 方法后进入该状态,需要其他线程调用 Object.notify()Object.notifyAll()LockSupport.unpark() 来唤醒。
  • TIMED_WAITING(计时等待):与 WAITING 类似,但有时间限制,例如调用 Thread.sleep(long millis)Object.wait(long timeout) 等方法。
  • TERMINATED(终止):线程执行完毕或者因异常退出。

2. 什么是守护线程?有什么作用?

题目分析:守护线程是 Java 线程机制中的一个特殊概念,考查对线程不同类型及其用途的理解。
答案
守护线程是一种特殊的线程,它的作用是为其他线程提供服务。当所有非守护线程结束时,守护线程会自动终止,即使它的任务还未完成。
在 Java 中,可以通过 setDaemon(true) 方法将线程设置为守护线程,且该方法必须在 start() 方法之前调用。例如:

Thread daemonThread = new Thread(() -> {while (true) {try {Thread.sleep(1000);System.out.println("Daemon thread is running");} catch (InterruptedException e) {e.printStackTrace();}}
});
daemonThread.setDaemon(true);
daemonThread.start();

守护线程常用于垃圾回收、监控等服务,比如 JVM 的垃圾回收线程就是一个典型的守护线程。

二、同步与锁类

1. 请比较 synchronizedReentrantLock 的异同

题目分析:这是多线程同步机制中的重点内容,synchronizedReentrantLock 是常用的同步手段,考查对它们的理解和使用场景的掌握。
答案
相同点

  • 都用于实现线程同步,保证同一时间只有一个线程可以访问共享资源。
  • 都具有可重入性,即同一个线程可以多次获取同一把锁而不会发生死锁。

不同点

  • 语法层面synchronized 是 Java 关键字,是内置的语言实现;ReentrantLock 是一个类,需要手动调用 lock()unlock() 方法来加锁和解锁。
// synchronized 示例
public class SynchronizedExample {private int count = 0;public synchronized void increment() {count++;}
}// ReentrantLock 示例
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private int count = 0;private final ReentrantLock lock = new ReentrantLock();public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}
}
  • 灵活性ReentrantLock 更加灵活,例如可以实现公平锁(new ReentrantLock(true)),还可以使用 tryLock() 方法尝试获取锁,避免线程长时间阻塞。
  • 锁的释放synchronized 会在同步块或方法执行完毕后自动释放锁;ReentrantLock 必须在 finally 块中手动调用 unlock() 方法释放锁,否则可能导致死锁。

2. 什么是死锁?如何避免死锁?

题目分析:死锁是多线程编程中常见且严重的问题,考查对死锁概念和解决方法的掌握。
答案
死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
死锁的产生需要满足四个必要条件:

  • 互斥条件:进程对所分配到的资源进行排他性使用,即在一段时间内某资源只由一个进程占用。
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
  • 循环等待条件:在发生死锁时,必然存在一个进程——资源的环形链,即进程集合 {P0,P1,P2,···,Pn} 中的 P0 正在等待一个 P1 占用的资源;P1 正在等待 P2 占用的资源,……,Pn 正在等待已被 P0 占用的资源。

避免死锁的方法有:

  • 破坏请求和保持条件:可以采用资源一次性分配的策略,即进程在运行前一次性申请它所需要的全部资源,在它的资源未满足前,不把它投入运行。
  • 破坏不剥夺条件:允许进程剥夺使用其它进程占有的资源。
  • 破坏循环等待条件:采用资源有序分配法,即把系统中的所有资源编号,进程在请求资源时,必须严格按资源编号的递增顺序进行,避免形成资源的环形链。
  • 使用定时锁:例如 ReentrantLocktryLock(long timeout, TimeUnit unit) 方法,在一定时间内无法获取锁时,线程可以放弃等待,避免死锁。

三、线程池类

1. 请简述 Java 中线程池的工作原理和主要参数

题目分析:线程池是 Java 多线程编程中的重要工具,考查对线程池内部机制和参数的理解。
答案
工作原理
线程池的核心思想是预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完毕后线程不会销毁,而是返回线程池等待下一个任务。这样可以避免频繁创建和销毁线程带来的性能开销。
线程池的主要工作流程如下:

  1. 当有新任务提交时,首先检查线程池中的核心线程数是否达到 corePoolSize,如果未达到,则创建新的核心线程来执行任务。
  2. 如果核心线程数已达到 corePoolSize,则将任务放入阻塞队列中。
  3. 如果阻塞队列已满,且线程池中的线程数未达到 maximumPoolSize,则创建新的非核心线程来执行任务。
  4. 如果线程池中的线程数已达到 maximumPoolSize,且阻塞队列已满,则根据拒绝策略来处理新任务。

主要参数

  • corePoolSize:核心线程数,线程池始终保持的线程数量。
  • maximumPoolSize:线程池允许的最大线程数。
  • keepAliveTime:非核心线程在空闲时的存活时间,超过该时间线程将被销毁。
  • TimeUnitkeepAliveTime 的时间单位。
  • BlockingQueue:阻塞队列,用于存储等待执行的任务。常见的阻塞队列有 ArrayBlockingQueueLinkedBlockingQueue 等。
  • ThreadFactory:线程工厂,用于创建线程。
  • RejectedExecutionHandler:拒绝策略,当线程池和阻塞队列都已满时,如何处理新提交的任务。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用者线程执行任务)等。

2. 如何合理配置线程池的参数?

题目分析:这是线程池使用中的关键问题,合理配置参数可以提高线程池的性能和稳定性。
答案
线程池参数的配置需要根据具体的业务场景和系统资源来决定,以下是一些参考原则:

  • corePoolSize
    • 对于 CPU 密集型任务(如计算、加密等),线程池的核心线程数可以设置为 CPU 核心数 + 1,这样可以充分利用 CPU 资源,避免线程上下文切换带来的开销。可以使用 Runtime.getRuntime().availableProcessors() 方法获取 CPU 核心数。
    • 对于 I/O 密集型任务(如文件读写、网络请求等),线程池的核心线程数可以设置得大一些,一般可以设置为 CPU 核心数 * 2,因为 I/O 操作会使线程阻塞,此时可以让其他线程继续执行任务。
  • maximumPoolSize
    • 一般情况下,maximumPoolSize 可以设置为比 corePoolSize 大一些,以应对突发的任务高峰。但也不宜设置得过大,否则会占用过多的系统资源。
  • BlockingQueue
    • 对于任务执行时间较短、任务数量较多的场景,可以使用有界队列(如 ArrayBlockingQueue),避免队列无限增长导致内存溢出。
    • 对于任务执行时间较长、任务数量较少的场景,可以使用无界队列(如 LinkedBlockingQueue),让任务在队列中等待执行。
  • keepAliveTime
    • 可以根据任务的执行频率和系统资源情况来设置,一般可以设置为几十秒到几分钟不等。如果任务执行频率较高,可以适当缩短 keepAliveTime;如果任务执行频率较低,可以适当延长 keepAliveTime
  • RejectedExecutionHandler
    • 根据业务需求选择合适的拒绝策略。如果对任务丢失不敏感,可以选择 AbortPolicy;如果希望调用者线程来执行任务,可以选择 CallerRunsPolicy

四、并发工具类类

1. 请简述 CountDownLatchCyclicBarrier 的区别和使用场景

题目分析CountDownLatchCyclicBarrier 是 Java 并发包中常用的同步工具,考查对它们的功能和使用场景的理解。
答案
区别

  • 计数机制CountDownLatch 的计数器是递减的,初始值为需要等待的线程数量,每个线程完成任务后调用 countDown() 方法将计数器减 1,当计数器为 0 时,等待的线程可以继续执行;CyclicBarrier 的计数器是递增的,初始值为需要等待的线程数量,每个线程到达屏障时调用 await() 方法,当计数器达到初始值时,所有等待的线程同时继续执行,并且计数器可以重置,重复使用。
  • 使用方式CountDownLatch 主要用于一个或多个线程等待其他线程完成任务;CyclicBarrier 主要用于多个线程相互等待,达到一个共同的屏障点后再继续执行。

使用场景

  • CountDownLatch:适用于一个主线程等待多个子线程完成任务的场景,例如在多线程下载中,主线程等待所有子线程下载完成后进行合并操作。
import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {int threadCount = 3;CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " is working");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown();}}).start();}latch.await();System.out.println("All threads have finished their work");}
}
  • CyclicBarrier:适用于多个线程需要同步到某个点后再继续执行的场景,例如多个运动员在起跑线等待发令枪响后同时起跑。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierExample {public static void main(String[] args) {int threadCount = 3;CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> System.out.println("All threads have reached the barrier"));for (int i = 0; i < threadCount; i++) {new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " is waiting at the barrier");barrier.await();System.out.println(Thread.currentThread().getName() + " has passed the barrier");} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}).start();}}
}

2. Semaphore 有什么作用?请举例说明

题目分析Semaphore 是 Java 并发包中用于控制并发访问数量的工具,考查对其功能和使用场景的理解。
答案
Semaphore 可以理解为一个信号量,它用于控制同时访问某个资源的线程数量。Semaphore 内部维护了一个计数器,线程在访问资源前需要先获取信号量(调用 acquire() 方法),计数器减 1;线程访问完资源后需要释放信号量(调用 release() 方法),计数器加 1。当计数器为 0 时,其他线程需要等待,直到有线程释放信号量。
使用场景包括限制并发访问资源的数量,例如数据库连接池、限流等。
以下是一个简单的示例,模拟多个线程同时访问有限的资源:

import java.util.concurrent.Semaphore;public class SemaphoreExample {private static final int RESOURCE_COUNT = 3;private static final int THREAD_COUNT = 5;private static final Semaphore semaphore = new Semaphore(RESOURCE_COUNT);public static void main(String[] args) {for (int i = 0; i < THREAD_COUNT; i++) {new Thread(() -> {try {// 获取信号量semaphore.acquire();System.out.println(Thread.currentThread().getName() + " has acquired the resource");// 模拟使用资源Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();} finally {// 释放信号量semaphore.release();System.out.println(Thread.currentThread().getName() + " has released the resource");}}).start();}}
}

在这个示例中,有 5 个线程尝试访问 3 个资源,通过 Semaphore 可以控制同时只有 3 个线程可以访问资源,其他线程需要等待。

以上面试题涵盖了 Java 多线程的多个方面,从基础概念到高级应用,对于 Java
高级研发工程师来说,需要深入理解并熟练掌握这些知识,才能在多线程编程中应对各种复杂的场景。

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

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

相关文章

Java Virtual Machine(JVM)

JVM跨平台原理 跨平台&#xff1a;一次编译&#xff0c;到处运行 本质&#xff1a;不同操作系统上运行的JVM不一样&#xff0c;只需要把java程序编译成一份字节码文件&#xff0c;JVM执行不同的字节码文件。 Java是高级语言&#xff0c;提前编译一下&#xff08;变成字节码文件…

duckdb导出Excel和导出CSV速度测试

运行duckdb数据库 D:>duckdb v1.2.0 5f5512b827 Enter “.help” for usage hints. Connected to a transient in-memory database. Use “.open FILENAME” to reopen on a persistent database. 生成模拟数据&#xff0c;10个列&#xff0c;100万行数据&#xff1b; --…

TCP/IP参考模型和网络协议

由于国防部担心他们一些重要的主机、路由器和互联网关可能会突然崩溃&#xff0c;所以网络必须实现的另一目标是网络不受子网硬件损失的影响&#xff0c;已经建立的会话不会被取消&#xff0c;而且整个体系结构必须相当灵活。 TCP/IP是一组用于实现网络互连的通信协议。Interne…

uniapp商场之订单模块【订单列表】

文章目录 前言一、准备静态结构(分包)二、Tabs滑动切换1.Tabs文字渲染2.点文字高亮切换3.swiper滑动切换三、Tabs页面跳转高亮四、订单列表渲染1.封装列表组件2.订单状态父传子3.封装请求API4.准备请求参数5.初始化调用6.页面渲染五、订单支付1.页面条件渲染2.事件绑定前言 …

【教程】MySQL数据库学习笔记(七)——多表操作(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【MySQL数据库学习】系列文章 第一章 《认识与环境搭建》 第二章 《数据类型》 第三章 《数据定义语言DDL》 第四章 《数据操…

Mysql数据库

一.数据定义语言DDL 一.概述 DDL用于定义和管理数据库的结构 DDL关键字&#xff1a;1.CREATE; 2.ALTER; 3.DROP 二.SQL命名规定和规范 1.标识符命名规则 2.标识符命名规范 三.库管理 1. CREATE DATABASE 数据库名; 2. CREATE DATABASE IF NOT EXISTS 数据库名; 3. CREATE…

C++,STL容器适配器,priority_queue:优先队列深入解析

文章目录 一、容器概览与核心特性核心特性速览二、底层实现原理1. 二叉堆结构2. 容器适配器架构三、核心操作详解1. 容器初始化2. 元素操作接口3. 自定义优先队列四、实战应用场景1. 任务调度系统2. 合并K个有序链表五、性能优化策略1. 底层容器选择2. 批量建堆优化六、注意事项…

django上传文件

1、settings.py配置 # 静态文件配置 STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR /static, ]上传文件 # 定义一个视图函数&#xff0c;该函数接收一个 request 参数 from django.shortcuts import render # 必备引入 import json from django.views.decorators.http i…

mapbox 从入门到精通 - 目录

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;mapbox 从入门到精通 文章目录 一、&#x1f340;总目录1.1 ☘️ mapbox基础1.2 ☘️…

【Qt】:概述(下载安装、认识 QT Creator)

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Qt 目录 一&#xff1a;&#x1f525; 介绍 &#x1f98b; 什么是 QT&#x1f98b; QT 发展史&#x1f98b; Qt版本&#x1f98b; QT 优点 一&#xff1a;&#x1f525; 搭建Qt开发环境 &#x1f9…

设置mysql的主从复制模式

mysql设置主从复制模式似乎很容易&#xff0c;关键在于1&#xff09;主库启用二进制日志&#xff0c;2&#xff09;从库将主库设为主库。另外&#xff0c;主从复制&#xff0c;复制些什么&#xff1f;从我现在获得的还很少的经验来看&#xff0c;复制的内容有表&#xff0c;用户…

halo发布文章的插件问题分析

前言 在准备发文到 halo 系统的时候提示错误如下&#xff0c;全是乱码 尝试将 halo 插件卸载后&#xff0c;再将插件目录下的文件全部删除 插件目录在 C:\Users\Administrator\.vscode\extensions\halo-dev.halo-1.3.0 然后再重新安装插件&#xff0c;在进行初始化的时候依然…

Spring Data Neo4j

文章目录 Spring Data Neo4j简介Neo4j-OGM与SDN的区别 开发体验版本说明项目地址项目结构创建项目配置连接信息激活事务管理器创建实体类Movie类Person类ActedIn关系类 创建Dao层service层测试案例CRUD TestPersonService TestActedIn Test 执行结果查询 Spring Data Neo4j简介…

Java发展史

JavaEE的由来 语言的诞生 Java的前身是Oak语言&#xff0c;其目的是搞嵌入式开发开发智能面包机 叮~~~&#x1f35e;&#x1f35e;&#x1f35e; 产品以失败告终 巅峰 网景公司需要网景浏览器打开网页&#xff0c;Oak->Java&#xff0c;进行前端开发&#xff08;相关技…

怎么让DeepSeek自动化写作文案

在数字化时代&#xff0c;内容创作已成为企业争夺用户注意力的核心竞争力。面对海量信息需求&#xff0c;企业往往面临内容创作效率低下、质量参差不齐、周期长等问题。如何用技术手段解决这些痛点&#xff0c;成为企业迫切需要破解的难题。今天&#xff0c;我们将以DeepSeek为…

Mysql之主从复制

目录 1.概述 2.工作原理 3.综合案例 3.1前期准备 3.2主库配置 3.3从库配置 3.4常见问题 3.4.1主从同步出现一下错误&#xff1a;Slave_IO_Running: No 3.4.1主从同步出现一下错误&#xff1a;Slave_IO_Running: Connecting? 3.5数据测试 1.概述 MySQL的主从复制&am…

从无序到有序:上北智信通过深度数据分析改善会议室资源配置

当前企业普遍面临会议室资源管理难题&#xff0c;预约机制不完善和临时会议多导致资源调度不合理&#xff0c;既有空置又有过度拥挤现象。 针对上述问题&#xff0c;上北智信采用了专业数据分析手段&#xff0c;巧妙融合楼层平面图、环形图、折线图和柱形图等多种可视化工具&a…

使用pyCharm创建Django项目

使用pyCharm创建Django项目 1. 创建Django项目虚拟环境&#xff08;最新版版本的Django) 使用pyCharm的创建项目功能&#xff0c;选择Django,直接创建。 2. 创建Django项目虚拟环境&#xff08;安装特定版本&#xff09; 2.1创建一个基础的python项目 2.2 安装指定版本的D…

RabbitMQ 在 Spring Boot中使用方式

文章目录 作用MQ docker 安装MQ使用RabbitMQ的整体架构及核心概念&#xff1a;RabbitMQ的整体架构及核心概念&#xff1a;消费者消息推送限制交换机与队列## 项目使用MQDirect: 直连模式Fanout: 广播模式Topic: 主题模式Headers: 头信息模式 使用DEMO地址异常问题记录 作用 Ra…

力扣动态规划-30【算法学习day.124】

前言 ###我做这类文章一个重要的目的还是记录自己的学习过程&#xff0c;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&#xff01;&#xff01;&#xff01; 习题 1.零钱兑换 题目链接:322. 零钱兑…