Java并发编程——线程池(基础,使用,拒绝策略,命名,提交方式,状态)

我是一个计算机专业研0的学生卡蒙Camel🐫🐫🐫(刚保研)
记录每天学习过程(主要学习Java、python、人工智能),总结知识点(内容来自:自我总结+网上借鉴)
希望大家能一起发现问题和补充,也欢迎讨论👏👏👏

文章目录

    • 线程池🏊
      • 线程池的好处👍
      • 线程池的创建🏗️
      • 线程池(ThreadPoolExecutor)常见参数🔢
      • 处理任务流程🔃
      • 拒绝策略⭐
        • 使用数据库任务表来自定义拒绝策略
      • 线程池中两种提交方式
      • 线程池命名♂️♀️
      • 线程池状态

线程池🏊

线程池是多线程应用中的一种资源管理技术,它旨在减少创建和销毁线程所带来的开销,并且通过复用已存在的线程来执行任务,提高响应速度。线程池提供了一种限制和管理资源(包括执行一个任务的线程)的方法。

线程池的好处👍

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的创建🏗️

  1. 使用ThreadPoolExecutor构造函数来创建自定义线程池(建议这种方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险)
  2. 通过使用 java.util.concurrent.Executors 工厂类创建

常用的线程池:

Executors 返回线程池对象特点
FixedThreadPool创建一个固定大小的线程池。如果所有线程都处于活动状态,新任务将在队列中等待,直到有线程可用。
CachedThreadPool创建一个可根据需要创建新线程的线程池,但在前一次构造的线程可用时将重用它们。适用于执行大量短期异步任务的应用程序。
SingleThreadExecutor创建一个单线程化的 Executor,它会确保所有任务都在同一个线程中按顺序执行。
ScheduledThreadPool创建一个支持定时及周期性的任务执行的线程池,类似于 Timer
WorkStealingPool创建一个具有多个任务队列的工作窃取线程池,适用于处理大量可并行的任务。

线程池对象的构造方法:

// LinkedBlockingQueue有界队列
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}// LinkedBlockingQueue 无界队列
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}// SynchronousQueue同步队列
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}// DelayedWorkQueue(延迟阻塞队列)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());}...
}

线程池(ThreadPoolExecutor)常见参数🔢

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||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;
}

参数解释

参数解释
corePoolSize线程池的核心线程数量:任务队列未达到队列容量时,最大可以同时运行的线程数量。
maximumPoolSize线程池的最大线程数:任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
keepAliveTime当线程数大于核心线程数时,多余的空闲线程存活的最长时间
unit时间单位,使用java.util.concurrent.TimeUnit枚举值来指定时间单位
workQueue任务队列,用来储存等待执行任务的队列
threadFactory线程工厂,用来创建线程,一般默认即可
handler拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务(常见的拒绝策略包括抛出异常、丢弃任务、执行者自身运行任务等。)

工作队列(workQueue):用于存放待执行的任务的阻塞队列。可以使用BlockingQueue接口的任何实现,常见的有:

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:一个基于链表结构的有界阻塞队列。如果构造时未指定容量,则默认容量为Integer.MAX_VALUE,即视为无界队列
  • SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作。
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  • **LinkedBlockingDeque:**一个由链表结构组成的双端阻塞队列。

img

处理任务流程🔃

ThreadPoolExecutor中最关键的execute源码:

public void execute(Runnable command) {// 先检查传入的command是否为空,若空则报错if (command == null)throw new NullPointerException();int c = ctl.get(); // 原子操作,返回线程池状态和线程计数的控制字段值// 1. 如果线程数少于corePoolSize,调用addWorker创建核心线程数if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// 2. 如果线程数大于corePoolSize,将任务添加进workQueue队列if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 2.1 如果检查isRunning的状态为false,则remove这个任务,然后执行拒绝策略if (! isRunning(recheck) && remove(command))reject(command);// 2.2 线程池处于running状态,但是没有线程,则创建线程else if (workerCountOf(recheck) == 0)addWorker(null, false);}// 3.如果放入workQueue失败,则创建非核心线程执行任务,如果创建失败(当前线程总数不小于maximumPoolSize),就会拒绝else if (!addWorker(command, false))reject(command);
}

img

拒绝策略⭐

在Java的ThreadPoolExecutor中,当线程池无法处理新提交的任务时(例如,线程池已关闭或者队列已满),可以配置不同的拒绝策略来决定如何处理这些任务。

拒绝策略详细
ThreadPoolExecutor.AbortPolicy**默认的拒绝策略。**抛出 RejectedExecutionException来拒绝新任务的处理。
ThreadPoolExecutor.CallerRunsPolicy如果使用这种策略,那么当任务被拒绝时,该任务将在调用者的线程中执行,即直接在调用execute方法的线程中运行这个任务。这可能会降低任务提交线程的速度,从而减缓任务提交的速度,给线程池一些时间来处理队列中的任务。
ThreadPoolExecutor.DiscardPolicy该策略会悄悄地丢弃无法处理的任务,既不会抛出异常也不会通知调用者。因此,使用此策略时需要注意可能丢失任务的情况。
ThreadPoolExecutor.DiscardOldestPolicy此策略会丢弃队列中最旧的一个未处理任务,并尝试重新提交当前任务。这意味着最老的任务将从队列中移除,然后尝试将当前任务添加到队列中进行处理。
自定义拒绝策略通过实现接口可以自定义任务拒绝策略。

在日常开发中,我们不希望任务被丢弃,我们就会使用CallerRunsPolicy拒绝策略。

// 被拒绝任务的处理程序,直接在execute方法的调用线程中运行被拒绝的任务,除非执行器已关闭,在这种情况下,任务将被丢弃。
public static class CallerRunsPolicy implements RejectedExecutionHandler {/*** Creates a {@code CallerRunsPolicy}.*/public CallerRunsPolicy() { }/***在调用者的线程中执行任务r,除非执行器已关闭,在这种情况下,任务将被丢弃* @param r 请求执行的可运行任务* @param e 试图执行此任务的执行器*/public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {r.run();}}
}

又源代码可知:只要当前程序不关闭就会使用执行execute方法的线程执行该任务。

但是会有一定风险

如果走到CallerRunsPolicy的任务是个非常耗时的任务,且处理提交任务的线程是主线程,可能会导致主线程阻塞,影响程序的正常运行。也可能会造成死锁(如果调用者线程正在等待线程池中的某个任务完成,而这个任务又依赖于调用者线程能够继续运行,那么就会形成循环依赖,进而导致死锁。)

解决方案:

  1. 继续采用CallerRunsPolicy拒绝策略

  2. 在内存允许的情况下,我们可以增加阻塞队列BlockingQueue的大小并调整堆内存以容纳更多的任务,确保任务能够被准确执行。

    1. 1为了充分利用 CPU,我们还可以调整线程池的maximumPoolSize (最大线程数)参数,这样可以提高任务处理速度,避免累计在 BlockingQueue的任务过多导致内存用完。
  3. 持久化思路(自定义)

    1. 设计一个数据库表来存储到数据库中

    2. 使用Redis缓存

    3. 提交到消息队列

使用数据库任务表来自定义拒绝策略
  1. 实现RejectedExecutionHandler接口自定义拒绝策略,自定义拒绝策略负责将线程池暂时无法处理(此时阻塞队列已满)的任务入库(保存到 MySQL 中)。注意:线程池暂时无法处理的任务会先被放在阻塞队列中,阻塞队列满了才会触发拒绝策略。
public class DatabaseRejectedExecutionHandler implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {// 保存任务到数据库的方法saveTaskToDatabase(r);}private void saveTaskToDatabase(Runnable task) {// 实现保存任务到数据库的逻辑// 可以使用JDBC或者其他ORM框架如MyBatis、Hibernate等}
}
  1. 继承BlockingQueue实现一个混合式阻塞队列,该队列包含 JDK 自带的ArrayBlockingQueue。另外,该混合式阻塞队列需要修改取任务处理的逻辑,也就是重写take()方法,取任务时优先从数据库中读取最早的任务,数据库中无任务时再从 ArrayBlockingQueue中去取任务。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;public class HybridBlockingQueue<E> implements BlockingQueue<E> {private final ArrayBlockingQueue<E> queue;public HybridBlockingQueue(int capacity) {this.queue = new ArrayBlockingQueue<>(capacity);}// 其他BlockingQueue接口方法的实现...@Overridepublic E take() throws InterruptedException {// 尝试从数据库中取任务E taskFromDb = takeFromDatabase();if (taskFromDb != null) {return taskFromDb;} else {// 如果数据库中没有任务,则从queue中取return queue.take();}}private E takeFromDatabase() {// 实现从数据库中取任务的逻辑// 注意这里需要根据你的E类型来确定如何反序列化或转换成任务对象return null; // 返回null表示数据库中没有任务}@Overridepublic boolean offer(E e) {return queue.offer(e);}@Overridepublic int remainingCapacity() {return queue.remainingCapacity();}// ...其他必须实现的方法,例如put(), poll(), etc.
}

线程池中两种提交方式

  1. execute`方法

void execute(Runnable command): Executor接口中的方

  1. submit方法

<T> Future<T> submit(Callable<T> task);

<T> Future<T> submit(Runnable task, T result);

Future<?> submit(Runnable task);

两个方法区别:

  1. 异常处理

  2. 使用execute()时,未捕获异常导致线程终止,线程池创建新线程替代

  3. 使用submit()时,异常被封装在Future中,线程继续复用。(更加灵活的错误处理机制,允许调用者决定如何处理异常)

  4. 接受参数

  5. 返回值

线程池命名♂️♀️

给线程池命名可以帮助你在调试和监控多线程应用程序时更容易识别不同的线程池。默认情况下创建的线程名字类似 pool-1-thread-n 这样的,没有业务含义,不利于我们定位问题。

  1. 使用ThreadFactory来创建一个带有自定义名称的线程池:
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;/*** 线程工厂,它设置线程名称,有利于我们定位问题。*/
public final class NamingThreadFactory implements ThreadFactory {private final AtomicInteger threadNum = new AtomicInteger();private final String name;/*** 创建一个带名字的线程池生产工厂*/public NamingThreadFactory(String name) {this.name = name;}@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setName(name + " [#" + threadNum.incrementAndGet() + "]");return t;}
}

线程池状态

线程池有5种状态:

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
线程池状态详细介绍
RUNNING表示线程池处于正常运行状态,可以接受新的任务并处理已提交的任务。
SHUTDOWN表示线程池不再接受新任务,但会继续执行已经提交的任务直到所有任务完成。通过调用 shutdown() 方法进入此状态。
STOP表示线程池不再接受新任务,并尝试停止正在执行的所有任务。通过调用 shutdownNow() 方法进入此状态。
TIDYING表示所有任务都已完成,工作线程数量为零,即将进入 TERMINATED 状态。这是一个短暂的过渡状态,在这个状态下,terminated() 钩子方法会被调用。
TERMINATED表示线程池完全终止,所有的任务都已完成并且所有的工作线程都已被销毁。

shutdownNow为STOP,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。它试图终止线程的方法是通过调用 Thread.interrupt() 方法来实现的,但是这种方法的作用有限,如果线程中没有sleep、wait、Condition、定时锁等应用,interrupt()方法是无法中断当前的线程的。所以,shutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

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

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

相关文章

Linux第二课:LinuxC高级 学习记录day04

6、shell中的语句 6.3、结构性语句 6.3.1、if if…then…fi 1、结构 1&#xff09;基本结构 if 表达式 then 命令表 fi if [ 表达式 ] // 【】两侧有空格 then 命令表 fi 2&#xff09;分层结构 if 表达式 then 命令表1 else 命令表2 fi 3&#xff09;嵌套结构 if …

tomcat文件目录讲解

目录的用处 bin&#xff1a;tomcat的可执行命令&#xff0c;比如&#xff1a;tomcat的启动停止命令&#xff0c;也包含其他命令以及.bat&#xff08;Windows执行的命令&#xff09;和.sh&#xff08;Linux操作系统执行的命令&#xff09;文件config:关于tomcat的配置&#xff0…

【Rust自学】12.4. 重构 Pt.2:错误处理

12.4.0. 写在正文之前 第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print)&#xff0c;是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。 这个项目分为这么几步&#xff1a; 接收命令行参数读取…

梁山派入门指南4——定时器使用详解,包括定时器中断、PWM产生、输入捕获测量频率

梁山派入门指南4——定时器使用详解&#xff0c;包括定时器中断、PWM产生、输入捕获测量频率 1. 定时器概览2.基本定时器2.1 基本定时器介绍2.2 梁山派上的基本定时器开发2.2.1. 了解梁山派上的基本定时器资源&#xff08;实际上我们以及在上面了解过了&#xff09;2.2.2. 配置…

excel仅复制可见单元格,仅复制筛选后内容

背景 我们经常需要将内容分给不同的人&#xff0c;做完后需要合并 遇到情况如下 那是因为直接选择了整列&#xff0c;当然不可以了。 下面提供几种方法&#xff0c;应该都可以 直接选中要复制区域然后复制&#xff0c;不要选中最上面的列alt;选中可见单元格正常复制&#xff…

JVM类加载器(附面试题)

什么是类加载器 类加载器&#xff08;ClassLoader&#xff09; 是 Java 虚拟机&#xff08;JVM&#xff09;中的一个组件&#xff0c;用于在运行时将字节码文件加载到内存中&#xff0c;并将其转换为 JVM 可以执行的二进制数据结构。 字节码文件通常是以.class为扩展名的文件…

FFmpeg硬件解码

使用FFmpeg进行硬件解码时&#xff0c;通常需要结合FFmpeg的API和硬件加速API&#xff08;如CUDA、VAAPI、DXVA2等&#xff09;。以下是一个简单的C代码示例&#xff0c;展示如何使用FFmpeg进行硬件解码。这个示例使用了CUDA作为硬件加速的后端。 1. 安装FFmpeg和CUDA 确保你…

Linux 进程前篇(冯诺依曼体系结构和操作系统)

目录 一.冯诺依曼体系结构 1.概念 2.硬件层面的数据流 3.总结加补充 二.操作系统 (Operating System) 1.概念 2.设计OS的目的 3.定位 4.操作系统的管理 5.计算机体系的层状结构 在我们认识进程之前&#xff0c;我们先了解什么是冯诺依曼体系结构 一.冯诺依曼体系结构…

Redis 3.2.1在Win10系统上的安装教程

诸神缄默不语-个人CSDN博文目录 这个文件可以跟我要&#xff0c;也可以从官网下载&#xff1a;https://github.com/MicrosoftArchive/redis/releases 这个是微软以前维护的Windows版Redis安装包&#xff0c;如果想要比较新的版本可以从别人维护的项目里下&#xff08;https://…

Unity-Mirror网络框架-从入门到精通之RigidbodyPhysics示例

文章目录 前言示例一、球体的基础配置二、三个球体的设置差异三、示例意图LatencySimulation前言 在现代游戏开发中,网络功能日益成为提升游戏体验的关键组成部分。本系列文章将为读者提供对Mirror网络框架的深入了解,涵盖从基础到高级的多个主题。Mirror是一个用于Unity的开…

JAVA实现五子棋小游戏(附源码)

文章目录 一、设计来源捡金币闯关小游戏讲解1.1 主界面1.2 黑棋胜利界面1.3 白棋胜利界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载更多优质源码分享 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/145161039 JA…

深度学习图像算法中的网络架构:Backbone、Neck 和 Head 详解

深度学习已经成为图像识别领域的核心技术&#xff0c;特别是在目标检测、图像分割等任务中&#xff0c;深度神经网络的应用取得了显著进展。在这些任务的网络架构中&#xff0c;通常可以分为三个主要部分&#xff1a;Backbone、Neck 和 Head。这些部分在整个网络中扮演着至关重…

Jmeter如何进行多服务器远程测试

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 JMeter是Apache软件基金会的开源项目&#xff0c;主要来做功能和性能测试&#xff0c;用Java编写。 我们一般都会用JMeter在本地进行测试&#xff0c;但是受到单…

摄像头模块在狩猎相机中的应用

摄像头模块是狩猎相机的核心组件&#xff0c;在狩猎相机中发挥着关键作用&#xff0c;以下是其主要应用&#xff1a; 图像与视频拍摄 高清成像&#xff1a;高像素的摄像头模块可确保狩猎相机拍摄出清晰的图像和视频&#xff0c;能够捕捉到动物的毛发纹理、行为细节及周围环境的…

[操作系统] 深入理解操作系统的概念及定位

概念 任何计算机系统都包含⼀个基本的程序集合&#xff0c;称为操作系统(OS)。 其核心功能如图片所示&#xff0c;包括&#xff1a; 内核 (Kernel)&#xff1a; 内核是操作系统的核心部分&#xff0c;被认为是狭义上的操作系统&#xff0c;直接与硬件打交道。负责进程管理、内…

Jmeter 简单使用、生成测试报告(一)

一、下载Jmter 去官网下载&#xff0c;我下载的是apache-jmeter-5.6.3.zip&#xff0c;解压后就能用。 二、安装java环境 JMeter是基于Java开发的&#xff0c;运行JMeter需要Java环境。 1.下载JDK、安装Jdk 2.配置java环境变量 3.验证安装是否成功&#xff08;java -versio…

postgresql分区表相关问题处理

1.使用pg_cron按日创建分区表&#xff0c;会出现所在数据库对应用户权限不足的问题。 原因是pg_cron运行在postgres数据库中&#xff0c;是用superuser进行执行的&#xff0c;对应的分区表的owner为postgres&#xff0c;所以需要单独授权对表的所有操作权限。不知道直接改变ow…

Agent一键安装,快速上手Zabbix监控!

目录 一、Linux操作系统部署Agent环境配置1、防火墙配置2、永久关闭selinux yum方式安装1、配置zabbix仓库2、安装agent3、配置 Zabbix-Agent 指向 Zabbix-Server4、启动agent服务 二进制包安装1、下载二进制包2、创建用户和目录及更改属主&#xff08;组&#xff09;3、解压二…

内存与缓存:保姆级图文详解

文章目录 前言1、计算机存储设备1.1、硬盘、内存、缓存1.2、金字塔结构1.3、数据流通过程 2、数据结构内存效率3、数据结构缓存效率 前言 亲爱的家人们&#xff0c;创作很不容易&#xff0c;若对您有帮助的话&#xff0c;请点赞收藏加关注哦&#xff0c;您的关注是我持续创作的…

【大数据】机器学习-----模型的评估方法

一、评估方法 留出法&#xff08;Holdout Method&#xff09;&#xff1a; 将数据集划分为训练集和测试集两部分&#xff0c;通常按照一定比例&#xff08;如 70% 训练集&#xff0c;30% 测试集&#xff09;。训练集用于训练模型&#xff0c;测试集用于评估模型性能。优点&…