Java 线程池详解

序言

在高并发编程中,线程池是一个非常重要的组件。它不仅能够有效地管理和复用线程资源,还可以提升应用程序的性能和稳定性。本文将详细介绍Java中的线程池机制,以及如何正确地使用线程池。

一、什么是线程池

线程池是一组已经初始化并等待执行任务的线程集合。通过使用线程池,我们可以避免频繁地创建和销毁线程,从而节省资源和减少系统开销。线程池的核心思想是通过复用线程来提高性能。

二、为什么使用线程池

  • 资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

三、Java 中的线程池

Java 提供了 java.util.concurrent 包来支持并发编程。其中有几种方法创建线程池

1、通过 Executors 工厂类的静态方法创建线程池:

newFixedThreadPool
 /**
  * 创建一个可重用固定数量线程的线程池,这些线程操作于共享的无界队列。
   * 在任何时候,最多有 {@code nThreads} 个线程会处于活动状态处理任务。
   * 当所有线程都在活动时,如果提交了额外的任务,它们将在队列中等待,
   * 直到有线程可用。
   * 如果在关闭前,任何线程因执行失败而终止,将根据需要创建一个新的线程来执行后续任务。
   * 线程池中的线程将持续存在,直到显式调用 {@link ExecutorService#shutdown shutdown} 方法关闭它。
   *
   * @param nThreads 线程池中的线程数量
   * @return 新创建的线程池
   * @throws IllegalArgumentException 如果 {@code nThreads <= 0}
   */
 public static ExecutorService newFixedThreadPool(int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
 }
newSingleThreadExecutor

/**
 * 创建一个使用单一工作线程的ExecutorService来执行任务。
 * 此执行器服务使用单一线程以顺序方式处理任务,确保每次只执行一个任务。
 * 即使在执行前一个任务时因失败导致线程终止,在关闭前也会创建新的线程继续执行后续任务。
 * 与等效的{@code newFixedThreadPool(1)}不同,返回的执行器保证不能重新配置为使用额外的线程。
 *
 * @return 新创建的单线程ExecutorService
 */
public static ExecutorService newSingleThreadExecutor() {
   return new FinalizableDelegatedExecutorService
       (new ThreadPoolExecutor(1, 1,
                               0L, TimeUnit.MILLISECONDS,
                               new LinkedBlockingQueue<Runnable>()));
}
其中这两个线程池里面使用到的阻塞队列如下:
// 队列的最大长度是Integer的最大整数
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}
newCachedThreadPool
/**
 * 创建一个可根据需要创建新线程的线程池,同时会重用之前已构建的空闲线程。
 * 这种线程池通常能提升执行大量短生命周期异步任务程序的性能。
 * 调用{@code execute}方法时,如果存在可用的先前构建的线程则会重用它们。
 * 若无现有线程可用,将创建新线程并加入到线程池中。
 * 在线程闲置六十秒后,线程将被终止并从缓存中移除。
 * 因此,长时间处于空闲状态的线程池不会消耗任何资源。
 * 注意:可以通过{@link ThreadPoolExecutor}构造器创建具有类似特性但细节不同的(例如超时参数)线程池。
 *
 * @return 新创建的线程池实例
 */
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

注:一般在工程上不建议使用它们创建线程池,阿里java开发手册

在这里插入图片描述

2、通过 ScheduledExecutorService 创建定时任务线程池:

ScheduledExecutorService 是 ExecutorService 的子接口,专门用于支持定时及周期性任务执行的线程池。可以通过 Executors.newScheduledThreadPool(int corePoolSize) 方法创建:

 /**
 * 创建一个可以调度命令在指定延迟后运行,或周期性执行的线程池。
 * 
 * @param corePoolSize 即使在空闲时,线程池中也要保持的线程数量
 * @return 新创建的ScheduledExecutorService实例,即计划线程池
 * @throws IllegalArgumentException 如果corePoolSize小于0
 */
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

3、通过 ThreadPoolExecutor 构造函数直接创建:

使用 ThreadPoolExecutor 的构造函数可以更加灵活地配置线程池,例如指定核心线程数、最大线程数、线程存活时间、工作队列等参数,如下所示:

 public ThreadPoolExecutor(
 	int corePoolSize, // 核心线程数
	int maximumPoolSize, // 最大线程数
	long keepAliveTime, // 线程存活时间
	TimeUnit unit, // 线程存活时间单位
	BlockingQueue<Runnable> workQueue, // 阻塞队列
	ThreadFactory threadFactory, // 线程工厂
	RejectedExecutionHandler handler //拒绝策略
)

四、ThreadPoolExecutor 线程池的执行过程

流程图是这样的:
在这里插入图片描述
整体流程就是:

1、任务来的时候,如果当前线程数小于核心线程数,则创建线程执行任务
2、否则,将任务添加到阻塞队列
3、如果队列已满
	case1:如果当前线程数小于最大线程数,则创建线程执行任务
	case2:如果当前线程数大于等于最大线程数,则执行拒绝策略

其实这个流程整体上来说是没什么问题的,也是大多数面试者回答该问题的标准答案。
但是这里有一个小问题,就是当我们把核心线程数设置为0的时候,声明的阻塞队列长度随便给一个大于0的值即可(此处我使用LinkedBlockingQueue队列,它的默认长度是Integer的最大值)。
我们来看看下面的代码,大家觉得会输出什么?

public static void testThreadPool() {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, 10, 60, 
            TimeUnit.SECONDS, 
            new LinkedBlockingQueue<>(),
            Executors.defaultThreadFactory(), 
            new ThreadPoolExecutor.AbortPolicy());
    threadPoolExecutor.execute(()->{
        System.out.println("线程池测试: " + new Date());
    });
}

如果按照上面流程图的步骤进行的话,什么也不会输出,因为此任务会加入到阻塞队列里面,对不对?
其实它输出了,结果如下
在这里插入图片描述
你现在可能有一个大大问号,其实我们可以从源码那里得到答案,源码如下:

public void execute(Runnable command) {
    // 检查传入任务是否为空,为空抛出异常
    if (command == null)
        throw new NullPointerException();
    // 获取当前线程池控制状态
    int c = ctl.get();
    // 1、如果当前线程数小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
        // 添加一个新的工作线程来执行任务
        // 添加成功,直接返回
        if (addWorker(command, true))
            return;
        // 再次获取线程池控制状态,防止并发
        c = ctl.get();
    }
    // 2、如果线程池处于运行状态,并且任务能够加入队列
    if (isRunning(c) && workQueue.offer(command)) {
        // 再次获取线程池控制状态,防止并发
        int recheck = ctl.get();
        // 2.1、如果线程池不再运行,并且任务能够从队列中移除,则拒绝任务
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 2.2、如果当前线程池没有运行的线程(此处可以打消你的问号)
        else if (workerCountOf(recheck) == 0)
            // 添加一个新的非核心工作线程
            addWorker(null, false);
    }
    // 3、添加一个新的非核心工作线程,如果失败,拒绝任务
    else if (!addWorker(command, false))
        reject(command);
}

所以说,从源码的角度具体去分析线程池执行任务的流程如下:

1、任务来的时候,如果当前线程数小于核心线程数,则创建线程执行任务
2、否则,线程池处于运行状态并且任务能够加入队列
	case1:判断线程池运行状态,如果线程池状态不是运行的情况下,同时任务可以从队列移除,那么就拒绝该任务
	case2:判断当前是否有工作线程,如果没有,则创建一个非核心的工作线程
3、如果队列已满
	case1:如果当前线程数小于最大线程数,则创建线程执行任务
	case2:如果当前线程数大于等于最大线程数,则执行拒绝策略

五、线程池的拒绝策略

名称描述
AbortPolicy丢弃任务并抛出RejectedExecutionException异常。这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
CallerRunsPolicy由调用线程(提交任务的线程)处理该任务。这种情况是需要让所有任务都执行完毕那么就适合大量计算的任务类型去执行,多线程仅仅是增大吞吐量的手段,最终必须要让每个任务都执行完毕。
DiscardPolicy丢弃任务,但是不抛出异常。 使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。
DiscardOldestPolicy丢弃队列最前面的任务,然后重新提交被拒绝的任务。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。

六、线程池提交任务

线程池有两种提交任务的方式,

1、使用submit(Runnable task),有返回值

public static void testThreadPool() {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, 10, 60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
    Future<?> submit = threadPoolExecutor.submit(() -> {
        System.out.println("线程池测试submit: " + new Date());
        return 1;
    });
    try {
        Object o = submit.get();
        System.out.println(o);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } catch (ExecutionException e) {
        throw new RuntimeException(e);
    }
}

2、使用execute(Runnable task),没有返回值

public static void testThreadPool() {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, 10, 60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
    threadPoolExecutor.execute(()->{
        System.out.println("线程池测试execute: " + new Date());
    });
}

七、线程池的状态

名称描述
RUNNING会接收新任务并且会处理队列中的任务
SHUTDOWN不会接收新任务并且会处理队列中的任务,任务处理完后中断所有线程
STOP不会接收新任务并且不会处理队列中的任务,直接中断所有线程
TIDYING所有任务都已终止,workerCount为0
TERMINATEDterminated()执行完成之后就会转变为TERMINATED

这五种状态之间的转换

RUNNING -> SHUTDOWN:调用shutdown方法

RUNNING or SHUTDOWN -> STOP:调用shutdownNow方法

SHUTDOWN -> TIDYING:阻塞队列为空,线程池中工作线程数为0

STOP-> TIDYING:线程池中工作线程数为0

TIDYING -> TERMINATED:执行erminated方法

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ftp pool 功能分析及 golang 实现

本文探究一种轻量级的 pool 实现 ftp 连接。 一、背景 简要介绍&#xff1a;业务中一般使用较多的是各种开源组件&#xff0c;设计有点重&#xff0c;因此本文探究一种轻量级的 pool 池的思想实现。 期望&#xff1a;设置连接池最大连接数为 N 时&#xff0c;批量执行 M 个 F…

超时导致SparkContext构造失败的问题探究

文章目录 1.前言2. 基于事故现场对问题进行分析2.1 日志分析2.2 单独测试Topology代码试图重现问题 3. 源码解析3.1 Client模式和Cluster模式下客户端的提交和启动过程客户端提交时在两种模式下的处理逻辑ApplicationMaster启动时在两种模式下的处理逻辑 3.2 两种模式下的下层角…

OSPF.综合实验

1、首先将各个网段基于172.16.0.0 16 进行划分 1.1、划分为4个大区域 172.16.0.0 18 172.16.64.0 18 172.16.128.0 18 172.16.192.0 18 四个网段 划分R4 划分area2 划分area3 划分area1 2、进行IP配置 如图使用配置指令进行配置 ip address x.x.x.x /x 并且将缺省路由…

uniapp编译成h5后接口请求参数变成[object object]

问题&#xff1a;uniapp编译成h5后接口请求参数变成[object object] 但是运行在开发者工具上没有一点问题 排查&#xff1a; 1&#xff1a;请求参数&#xff1a;看是否是在请求前就已经变成了[object object]了 结果&#xff1a; 一切正常 2&#xff1a;请求头&#xff1a;看…

2024辽宁省数学建模C题【改性生物碳对水中洛克沙胂和砷离子的吸附】原创论文分享

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024 年辽宁省大学数学建模竞赛C题改性生物碳对水中洛克沙胂和砷离子的吸附完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃…

OpenGL笔记九之彩色三角形与重心插值算法

OpenGL笔记九之彩色三角形与重心插值算法 —— 2024-07-07 晚上 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记九之彩色三角形与重心插值算法1.运行3.main.cpp 1.运行 3.main.cpp 代码 #include <iostream>#define DEBUG//注意&#xff1a;glad…

虚拟机centos连接xshell

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…

VsCode远程ssh连接失败:Could not establish connection to XXX

一、问题描述 在VsCode中按下"F1"&#xff0c;选择Remote-SSH:Connect to Host 选择一个已经配置好的SSH主机&#xff0c;比如我选择的是192.168.0.104&#xff1a; 结果提示&#xff1a;Could not establish connection to XXX 二、解决方法 观察VsCode的输出信息…

几何建模-Parasolid中GO功能使用

1.背景介绍 1.1 Parasolid和它的接口间关系 1.2 什么是GO GO全称是Graphical Output.你的程序需要在屏幕或者打印设备上显示模型数据时。在需要使用PK中的某个渲染函数时创建图形显示数据时&#xff0c;Parasolid会调用GO相关的函数。GO函数会输出绘图指令给你的应用程序提供…

《昇思25天学习打卡营第20天|onereal》

应用实践/LLM原理和实践/基于MindSpore的GPT2文本摘要 基于MindSpore的GPT2文本摘要 数据集加载与处理 数据集加载 本次实验使用的是nlpcc2017摘要数据&#xff0c;内容为新闻正文及其摘要&#xff0c;总计50000个样本。 数据预处理 原始数据格式&#xff1a; article: [CLS…

MySQL-基础点

目录 MySQL概念 数据库三大范式是什么&#xff1f; blob 和 text 有什么区别&#xff1f; DATETIME 和 TIMESTAMP 的异同&#xff1f; MySQL 中 in 和 exists 的区别&#xff1f; MySQL 里记录货币用什么字段类型比较好&#xff1f; MySQL 怎么存储 emoji? 用过哪些 M…

MongoDB7出现:Windows下使用mongo命令提示不是内部或外部命令

确保环境变量添加正确的情况&#xff0c;仍然出现这种问题。如果安装的是新版本&#xff0c;则大概率是新版本mongodb的bin里面没有mongo命令 解决方案&#xff1a; 下载mongodb shell 下载链接 把shell的命令放进来 启用命令&#xff1a;mongosh

浅谈数学模型在UGC/AIGC游戏数值调参中的应用(AI智能体)

浅谈数学模型在UGC/AIGC游戏数值调参中的应用 ygluu 卢益贵 关键词&#xff1a;UGC、AIGC、AI智能体、大模型、数学模型、游戏数值调参、游戏策划 一、前言 在策划大大群提出《游戏工厂&#xff1a;AI&#xff08;AIGC/ChatGPT&#xff09;与流程式游戏开发》讨论之后就已完…

每日一练,java

目录 描述示例 总结 描述 题目来自牛客网 •输入一个字符串&#xff0c;请按长度为8拆分每个输入字符串并进行输出&#xff1b; •长度不是8整数倍的字符串请在后面补数字0&#xff0c;空字符串不处理。 输入描述&#xff1a; 连续输入字符串(每个字符串长度小于等于100) 输…

JDK14新特征最全详解

JDK 14一共发行了16个JEP(JDK Enhancement Proposals&#xff0c;JDK 增强提案)&#xff0c;筛选出JDK 14新特性。 - 343: 打包工具 (Incubator) - 345: G1的NUMA内存分配优化 - 349: JFR事件流 - 352: 非原子性的字节缓冲区映射 - 358: 友好的空指针异常 - 359: Records…

网络规划设计师教程(第二版) pdf

网络规划设计师教程在网上找了很多都是第一版&#xff0c;没有第二版。 所以去淘宝买了第二版的pdf&#xff0c;与其自己独享不如共享出来&#xff0c;让大家也能看到。 而且这个pdf我已经用WPS扫描件识别过了&#xff0c;可以直接CtrlF搜索关键词&#xff0c;方便查阅。 链接…

为何你的旁路电容 总是无法滤除噪声

你一定遇过这样的困境 产品出现了噪声干扰 也找出干扰源了 但摆放了旁路电容 却总是解不掉干扰 请问原因为何? 先说结论 接地不好放太少颗电容值没有微调 在这篇文章 如何焊铜管 量测射频前端模块 我们提到了 不足的接地 会增加损耗 我们进一步 以阻抗的…

jmeter-beanshell学习9-放弃beanshell

写这篇时候道心不稳了&#xff0c;前面写了好几篇benashell元件&#xff0c;突然发现应该放弃。想回去改前面的文章&#xff0c;看了看无从下手&#xff0c;反正已经这样了&#xff0c;我淋了雨&#xff0c;那就希望别人也没有伞吧&#xff0c;哈哈哈哈&#xff0c;放在第九篇送…

在Linux系统实现瑞芯微RK3588部署rknntoolkit2进行模型转换

一、首先要先安装一个虚拟的环境 安装Miniconda包 Miniconda的官网链接:Minidonda官网 下载好放在要操作的linux系统,我用的是远程服务器的linux系统,我放在whl这个文件夹里面,这个文件夹是我自己创建的 运行安装 安装的操作都是yes就可以了 检查是否安装成功,输入下面…

LeetCode 面试题02.04.分割链表

LeetCode 面试题02.04.分割链表 C写法 思路&#x1f914;&#xff1a; ​ 将x分为两段&#xff0c;一段放小于x的值&#xff0c;另一段放大于x的值。开辟四个指针lesshead、lesstail、greaterhead、greatertail&#xff0c;head为哨兵位&#xff0c;防止链表为空时情况过于复杂…