并发编程 - 线程池中的常见面试题
目录
1. 线程池相比于线程有什么优点
2. 线程池的参数有哪些
3. 线程工厂有什么用
4. 说一下线程的优先级
5. 说一下线程池的执行流程
6. 线程池的拒绝策略有哪些
7. 如何实现自定义拒绝策略
8. 如何判断线程池中的任务是否执行完成
1. 线程池相比于线程有什么优点
有时候面试官也会这么问 : 让你介绍一下线程池,都是一样的回答方式。
在实际生活中,一般都是使用线程池,而不使用普通的线程,使用线程池有以下几个好处:
1. 降低资源消耗:线程池可以重复利用已经创建好的线程,避免了频繁创建和销毁线程带来的开销。其次呢,线程池可以有效地管理和控制线程的数量,避免线程过多而导致资源浪费。
2. 提高响应速度:线程池中的线程是预先创建好的,所以当任务来临时,它可以立即分配线程来进行处理,提高了任务的响应速度。
3. 提高系统稳定性:线程池可以限制并发时的线程数量,避免因为线程过多而导致资源耗尽或系统崩溃。它可以合理的控制系统的负载,以提高系统的稳定性。
4. 支持任务队列:线程池通常会使用任务队列来存储待执行的任务。当线程池中的线程都在执行任务时,新的任务可以被放入任务队列中排队等待执行,避免任务丢失或阻塞。
2. 线程池的参数有哪些
线程池 ThreadPoolExecutor 最多可以支持 7 个参数的设置(最少 5 个),底层源码:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||// maximumPoolSize 必须大于 0, 且必须大于 corePoolSize maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;
}
7 个参数的含义如下 :
1. corePoolSize:表示线程池的常驻核心线程数
这个值如果设为 0, 则表示在没有任务的时候,销毁线程池;如果大于 0,即使没有任务也会保证线程池的线程数量等于这个值。(这个值如果设置的比较小,则会频繁的创建和销毁线程;如果设置的比较大,则浪费系统资源,实际工作中根据业务场景来调整)
2. maximumPoolSize:表示线程池在任务最多的时候,最大可以创建的线程数
官方规定这个值必须大于 0。也必须大于等于 corePoolSize,(此值只有在任务比较多的时候,且任务队列已经存满的时候,才会用到)
3. keepAliveTime:表示临时线程(最大线程数-核心线程数)的存活时间
当线程池空闲时并且超过了这个时间,就会销毁临时线程,直到线程池中的线程数量等于 corePoolSize 为止。
4. unit:表示临时线程的存活单位
5. workQueue:表示线程池执行的任务队列
当线程池的所有线程都在处理任务时,新加入的任务就会缓存到任务队列中,排队等待执行。
6.threadFactory:表示线程的创建工厂
可以用来设置线程名称,线程优先级,以及线程的类型(前后台线程)等内容。
7. RejectedExecutionHandler:表示指定线程池的拒绝策略
当线程池中的核心线程数都在执行任务,任务队列此也已经满了,并且线程池中的线程数已经达到设置的最大线程数了(不能创建新线程了),就会使用到拒绝策略,它是一种限流保护的机制。
3. 线程工厂有什么用
通过线程工厂可以设置线程池中可以创建的最大线程数,设置线程的名称,线程的优先级以及线程的类型等内容。
ThreadFactory threadFactory = new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {// 创建线程池中的线程Thread thread = new Thread(r);// 设置线程名称thread.setName("Thread-" + r.hashCode());// 设置线程的优先级 (1-10) 最大 10thread.setPriority(Thread.MAX_PRIORITY);// 设置线程的类型 (前台/后台线程)thread.setDaemon(false);// ....return thread;}
};
4. 说一下线程的优先级
线程的优先级用整数表示,范围在 1-10 之间,并且数字越大表示的优先级越高,线程的默认优先级为 5。
设置线程优先级的作用:
线程优先级表示当前线程被调度的权重,也就是说线程的优先级越高,表示线程被被调度执行的可能性就越大,它会给线程调度器一个建议,具体是不是优先级越高的越先执行是不一定的。它这样设计的目的就是为了防止线程饿死。
在 Java 中,可以通过 Thread 类的 setPriority() 和 个体 getPriority() 来设置和获取线程的优先级
5. 说一下线程池的执行流程
线程池的执行流程通过下图进行梳理:

由于线程池是以懒加载的方式进行创建线程的,所以线程池创建第一个线程的时候,它普通的创建线程没有区别。
- 当任务来临时,它会先去判断一下实际线程数是否大于核心线程数,如果小于等于核心线程数,说明此时任务比较少,所以此时只需要创建一个新的线程来执行任务就完事了。
- 如果实际线程数大于核心线程数了,然后再去判断任务队列有没有满,如果队列没有满,那么就将新任务加入到任务队列中排队等待执行。
- 如果队列满了,那么再去判断实际线程数是否超过最大线程数,如果没有超过,那么创建一个新的线程来执行任务就完事了。
- 如果已经超过最大线程数了,那么就会执行拒绝策略。
【联系到快递公司】
- 当有新的快递来临了,我先看一下公司正式员工忙不忙的过来,如果有人空闲,那么安排空闲的人去干活。
- 如果公司的正式员工手头都忙不过来,此时检查一下快递仓库是否满了,如果没满,那么就放入快递仓库排队等待执行。
- 如果快递仓库也满了,老板此时还想挣这个钱,那么他会先计算出当前要招多少员工,才能保证利益最大化,如果当前公司总员工(正式+临时)还没有超过预算值,那么就招临时工来帮忙。
- 如果说招的临时工已经达到最大预算了,那么此时就会执行拒绝策略了,我不接单了。
6. 线程池的拒绝策略有哪些
拒绝策略一共有 5 种,线程池内置了 4 种和一种自定义拒绝策略,这四种拒绝策略分别是 :
1. AbortPolicy:终止策略,线程池会抛出异常并终止执行此任务;
在接不了订单的情况下,公司还有部门接单,那么领导就会开内部批斗大会,并终止快递流程。
2. CallerRunsPolciy:把任务交给添加此任务的(main)线程来执行;
在接不了订单的情况下,此时有一个卡车司机拉了一车快递来了,于是跟货车司机说:老弟啊,哥这也忙不过来了,你想不想挣个块钱发家致富,于是就将这个任务交给卡车司机来执行。
3. DiscardPolicy:忽略当前任务(最新的任务);
4. DiscardOldestPolicy:忽略任务队列中最早加入的任务,接收新任务(加入任务队列);
线程池默认的拒绝策略为 AbortPolicy 终止策略。
7. 如何实现自定义拒绝策略
代码示例:
// 创建 runnable 对象
// ...ThreadPoolExecutor threadPool =new ThreadPoolExecutor(1, 1,100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r,ThreadPoolExecutor executor) {// 执行自定义拒绝策略// 1.记录日志,方便问题的追溯// 2.通知相关人员来解决问题System.out.println("执行自定义拒绝策略");}});
// 添加并执行 3 个任务
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
拒绝策略里面一般可以做两件事:
- 记录日志,以便问题的追溯;
- 通过相关人员来解决问题。
如果面试官此时还问:你在实际工作中/你的项目中,你使用的是哪一种拒绝策略?
如果不知道怎么回答,建议回答自定义拒绝策略,因为它比较灵活,它可以去设置我想去设置的一些代码,我在里面呢,首先我可以把错误记录下来,其次我可以给任务队列发一个邮件,或者发一个 MQ(消息不丢失)
8. 如何判断线程池中的任务是否执行完成
" 牛客上有人在面试用友的时候,被问到王者荣耀 5 个人都加载完成才进入游戏,用 Java 应该怎么实现,就和这个问题如出一辙 "
判断线程池中的任务是否执行完成,有三种方法:(线程池提供了两种,还可以借助计数器)
1. 使用 getCompletedTaskCount() 统计已经执行完的任务,使用 getTaskCount() 获取线程池的总任务,将二者进行对比,如果相等就说明线程池的任务执行完了,反之没有执行完。
2. 使用 FutureTask 等待所有任务执行完。(类似于 Thread 里面的 join)
3. 借助计数器 CountDownLatch 或 CyclicBarrier 来实现。
① getCompletedTaskCount() 方式
private static void isCompleteByTaskCount(ThreadPoolExecutor threadPool) {while(threadPool.getCompletedTaskCount()!= threadPool.getTaskCount()) {// 如果 已完成任务数 != 总任务数, 就一直进行自旋}
}
这种方式的缺点:
- 如果已完成任务数 != 总任务数,那么就会一直自旋,过于消耗系统性能;
- 判断不够精准,因为线程池是公用的,如果这个时候有其他线程来添加了新的任务,那么总的任务数就变了,而我只想判断属于我的这几个任务知否执行完。
所以 getCompletedTaskCount() 并不是最优的实现方式。
② FutureTask 的方式
代码示例:
- 定义三个任务 (callable)
- 调用线程池提供的 submit 方法 (可执行回调任务/非回调任务)
- 调用任务的 get() 方法 (同步阻塞,类似于 thread.join())
public static void main(String[] args) throws ExecutionException,InterruptedException {// 创建固定大小的线程池ExecutorService executor = Executors.newFixedThreadPool(3);// 创建 3 个任务FutureTask<Integer> task1 = new FutureTask<>(() -> {System.out.println("task1 开始执行");Thread.sleep(2000);System.out.println("task1 执行结束");return 1;});FutureTask<Integer> task2 = new FutureTask<>(() -> {System.out.println("task2 开始执行");Thread.sleep(2000);System.out.println("task2 执行结束");return 2;});FutureTask<Integer> task3 = new FutureTask<>(() -> {System.out.println("task3 开始执行");Thread.sleep(2000);System.out.println("task3 执行结束");return 3;});// 提交 3 个任务给线程池executor.submit(task1);executor.submit(task2);executor.submit(task3);// 等所有任务执行完毕并获取结果 (同步阻塞)int ret1 = task1.get();int ret2 = task1.get();int ret3 = task1.get();
}
执行结果 >>>

③ 使用 CountDownLatch 或 CyclicBarrier
这个问题,回答前两种方式其实已经够了,此处多介绍一个 CountDownLatch。
代码示例:
- 递归枚举出所有 HTML 文件
- 通过线程池解析 HTML 文件
- 解析完所有的 HTML 文件后再执行后续的业务逻辑
public void runByThread() throws InterruptedException {ArrayList<File> files = new ArrayList<>();enumFile(INPUT_PATH,files); // 递归枚举出所有 HTML 文件ExecutorService executorService = Executors.newFixedThreadPool(4);CountDownLatch latch = new CountDownLatch(files.size());// 遍历文件,多线程解析文件for(File f:files) {executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("开始解析: " + f.getAbsolutePath());parseHTML(f); // 解析文件latch.countDown(); // 调用一次这个方法表示一个线程已经执行完了}});}latch.await(); // 阻塞(死等) -> 直到所有的线程都调用了 countDown()// 执行后续的业务逻辑System.out.println("索引制作完毕!");
}
那么上述三个步骤,4 个线程在执行解析文件的时候,很可能会存在这样一种情况:
这些文件都 submit 完了,但是还没有执行完,这是很有可能的。而想要执行后续的业务逻辑,那么就得保证这些文件全部被执行完了,所以此处 CountDownLatch 的作用就是为了保证这一点的
如何保证? 通过调用两个方法 :
- latch.await()
- latch.countDown()
第一个方法的作用是阻塞,它会一直进行死等,直到所有的线程都执行完了(全部都调用了 countDown() 方法),才结束阻塞。
第二个方法的作用是用来统计线程的执行情况,有一个线程执行完了,计数器就 + 1。
相关文章:
并发编程 - 线程池中的常见面试题
目录 1. 线程池相比于线程有什么优点 2. 线程池的参数有哪些 3. 线程工厂有什么用 4. 说一下线程的优先级 5. 说一下线程池的执行流程 6. 线程池的拒绝策略有哪些 7. 如何实现自定义拒绝策略 8. 如何判断线程池中的任务是否执行完成 1. 线程池相比于线程有什么优点 有…...
将多个单独的 Excel 文件合并成一个,并添加标题行
要将多个单独的 Excel 文件合并成一个,并添加标题行,可以使用 Python 的 pandas 库。以下是一个示例代码,假设要合并的 Excel 文件都在同一个文件夹中: import os import pandas as pd # 指定文件夹路径 folder_path path/to/fo…...
VPN pptp和l2tp协议破解
代码下载地址: https://download.csdn.net/download/m0_37567738/88215516?spm1001.2014.3001.5501...
4.3、Flink任务怎样读取Kafka中的数据
目录 1、添加pom依赖 2、API使用说明 3、这是一个完整的入门案例 4、Kafka消息应该如何解析 4.1、只获取Kafka消息的value部分 4.2、获取完整Kafka消息(key、value、Metadata) 4.3、自定义Kafka消息解析器 5、起始消费位点应该如何设置 5.1、earliest() 5.2、lat…...
C语言实例_和校验算法
一、算法介绍 和校验(Checksum)是一种简单的纠错算法,用于检测或验证数据传输或存储过程中的错误。它通过对数据进行计算并生成校验和,然后将校验和附加到数据中,在接收端再次计算校验和并进行比较,以确定…...
安全加密框架图——Oracle安全开发者
Oracle安全开发者 ACLs 设计 ACLs(访问控制列表)时,可以根据以下思路进行设计: 所有者文件权限:确定文件的所有者能够对文件执行哪些操作,如读取、写入、执行等。这可以根据文件的性质和拥有者的职责来决…...
Android databinding 被多次定义
一、报错: AndroidStudio运行代码时,编译器报 Type androidx.databinding.Bindable is defined multiple times...... 二、解决: 点击 Build -> Clean Project,关闭编译器再打开即可。 三、解决过程: 在使用Andro…...
云原生周刊:Kubernetes v1.28 新特性一览 | 2023.8.14
推荐一个 GitHub 仓库:Fast-Kubernetes。 Fast-Kubernetes 是一个涵盖了 Kubernetes 的实验室(LABs)的仓库。它提供了关于 Kubernetes 的各种主题和组件的详细内容,包括 Kubectl、Pod、Deployment、Service、ConfigMap、Volume、…...
机器学习之分类模型
机器学习之分类模型 概述分类模型逻辑回归最近邻分类朴素贝叶斯支持向量机决策树随机森林多层感知机基于集成学习的分类模型VotingBaggingStackingBlendingBoosting 概述 机器学习分类模型通过训练集进行学习,建立一个从输入空间 X X X到输出空间 Y Y Y(…...
学习Vue:创建第一个Vue实例
当您开始探索 Vue.js,第一步就是创建一个 Vue 实例。Vue 实例是 Vue.js 应用程序的核心构建块,它使您能够将数据与用户界面连接起来,实现动态交互。在本文中,我们将详细介绍如何创建您的第一个 Vue 实例。 步骤1:引入 …...
JavaFx基础学习【二】:Stage
一、介绍 窗口Stage为图中标绿部分: 实际为如下部分: 不同的操作系统表现的样式不同,以下都是以Windows操作系统为例,为了使大家更清楚Stage是那部分,直接看以下图,可能更清楚: 有点潦草&…...
C语言——动态内存函数(malloc、calloc、realloc、free)
了解动态内存函数 前言:一、malloc函数二、calloc函数三、realloc函数四、free函数 前言: 在C语言中,动态内存函数是块重要的知识点。以往,我们开辟空间都是固定得,数组编译结束后就不能继续给它开辟空间了࿰…...
Redis数据结构——Redis简单动态字符串SDS
定义 众所周知,Redis是由C语言写的。 对于字符串类型的数据存储,Redis并没有直接使用C语言中的字符串。 而是自己构建了一个结构体,叫做“简单动态字符串”,简称SDS,比C语言中的字符串更加灵活。 SDS的结构体是这样的…...
【计算机网络】TCP协议超详细讲解
文章目录 1. TCP简介2. TCP和UDP的区别3. TCP的报文格式4. 确认应答机制5. 超时重传6. 三次握手7. 为什么两次握手不行?8. 四次挥手9. 滑动窗口10. 流量控制11. 拥塞控制12. 延时应答13. 捎带应答14. 面向字节流15. TCP的连接异常处理 1. TCP简介 TCP协议广泛应用于可靠性要求…...
Salesforce特别元数据部署技巧
标准的picklist字段部署 <?xml version"1.0" encoding"UTF-8" standalone"yes"?> <Package xmlns"http://soap.sforce.com/2006/04/metadata"><types><members>Opportunity.StageName</members><…...
[前端系列第2弹]CSS入门教程:从零开始学习Web页面的样式和布局
在这篇文章中,我将介绍CSS的基本概念、语法、选择器、属性和值,以及如何使用它们来定义Web页面的外观和布局。还将给一些简单而实用的例子,可以跟着我一步一步地编写自己的CSS样式表。 目录 一、什么是CSS 二、CSS的语法 三、CSS的选择器 …...
非计算机科班如何丝滑转码?
转码,也就转行为程序员,已成为当今数字化时代的一种重要技能。随着科技的发展,越来越多的人开始意识到掌握编程技能的重要性,而非计算机科班出身的朋友们,想要丝滑转码,也许可以从以下几个方面入手。 一、明…...
亿发创新中医药信息化解决方案,自动化煎煮+调剂,打造智能中药房
传统中医药行业逐步复兴,同时互联网科技和人工智能等信息科技助力中医药行业逐步实现数字化转型。利用互联网、物联网、大数据等科技,实现现代科学与传统中医药的结合,提供智能配方颗粒调配系统、中药自动化调剂系统、中药煎配智能管理系统、…...
Vulnhub: MoneyBox: 1靶机
kali:192.168.111.111 靶机:192.168.111.194 信息收集 端口扫描 nmap -A -sC -v -sV -T5 -p- --scripthttp-enum 192.168.111.194 ftp匿名登录发现trytofind.jpg 目录爆破发现blogs目录 gobuster dir -u http://192.168.111.194 -w /usr/share/word…...
[国产MCU]-BL602开发实例-LCD1602 I2C驱动
LCD1602 I2C驱动 文章目录 LCD1602 I2C驱动1、LCD1602/LCD2004介绍2、硬件准备3、驱动实现本文将详细介绍如何在K210中驱动LCD1602/LCD2004 I2C显示屏。 1、LCD1602/LCD2004介绍 LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。它是由字符型液晶显示屏(LCD)、控制驱…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
