【Java知识】Java进阶-线程池深度解读

devtools/2024/10/18 11:52:52/

文章目录

线程池概述

Java线程池java.util.concurrent.ThreadPoolExecutor)是一种执行器(Executor),用于在一个后台线程中执行任务。线程池的主要目的是减少在创建和销毁线程时所产生的性能开销。通过重用已经创建的线程来执行新的任务,线程池提高了程序的响应速度,并且提供了更好的系统资源管理。

线程池的核心组件和概念:

  1. 核心线程数(Core Pool Size)
    线程池中始终保持的线程数量,即使它们处于空闲状态。

  2. 最大线程数(Maximum Pool Size)
    线程池中允许的最大线程数量。

  3. 工作队列(Work Queue)
    用于存放待执行任务的阻塞队列。

  4. 线程工厂(Thread Factory)
    用于创建新线程的工厂。可以用来设置线程的名称,以便在日志或监控中区分不同线程。

  5. 拒绝策略(Rejected Execution Handler)
    当任务太多,无法被线程池及时处理时,采取的策略。常见的拒绝策略包括丢弃任务、抛出异常、使用调用者线程执行任务等。

  6. 保持活动时间(Keep-Alive Time)
    非核心线程空闲时在终止前等待新任务的最长时间。

  7. 时间单位(Time Unit)
    保持活动时间的时间单位,如秒、毫秒等。

线程池的工作原理:

  1. 提交任务
    当一个任务被提交给线程池时,线程池会尝试使用空闲的核心线程来执行任务。

  2. 核心线程忙碌
    如果所有核心线程都忙碌,且工作队列已满,线程池会创建一个非核心线程来处理任务,直到达到最大线程数。

  3. 任务队列
    如果线程池中的线程都忙碌,且工作队列未满,新任务会被放入工作队列等待。

  4. 拒绝策略
    如果线程池和工作队列都达到了最大容量,线程池会使用拒绝策略来处理新任务。

  5. 线程复用
    线程池会重用执行完任务的线程,而不是销毁它们,以提高性能。

  6. 线程终止
    当非核心线程空闲时间超过指定的保持活动时间,且工作队列为空时,线程池会终止这些线程。

线程池的创建:

Java提供了Executors类来创建预定义配置的线程池

  • 固定大小的线程池

    java">int nThreads = 10;
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(nThreads);
    
  • 线程池

    java">ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    
  • 可缓存的线程池

    java">ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    
  • 定时及周期性任务的线程池

    java">ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
    

线程池的关闭:

当不再需要线程池时,应该正确地关闭它,以释放资源:

java">executorService.shutdown(); // 停止接收新任务,尝试完成已提交的任务
executorService.shutdownNow(); // 尝试立即终止所有正在执行的任务,并返回等待执行的任务列表

总结:

线程池是现代并发编程中不可或缺的一部分,它提供了一种有效的方式来管理并发任务。通过合理配置线程池,可以提高程序的性能和资源利用率。然而,不当的配置也可能导致资源浪费或性能问题,因此需要根据具体的应用场景来调整线程池的参数。

线程池种类以及用途

Java中的线程池类型主要可以通过java.util.concurrent包中的Executors类来创建,以下是几种常见的线程池类型及其用途:

  1. FixedThreadPool(固定大小的线程池

    • 特点:拥有固定数量的线程,这些线程会在池中一直存在,直到线程池被关闭。
    • 用途:适用于负载较重的服务器,处理请求比较平均,可以控制最大并发数,适合于短生命周期的长任务。
  2. SingleThreadExecutor(单线程执行器)

    • 特点:只有一个线程在工作,所有任务按顺序执行。
    • 用途:适用于需要保证任务顺序执行的场景,或者需要单个后台线程处理任务的场景。
  3. CachedThreadPool(可缓存的线程池

    • 特点:对于短生命周期的长任务非常合适,它会根据需要创建新线程,对于短生命周期的长任务非常合适,如果线程空闲超过60秒,则会被回收。
    • 用途:适用于短生命周期的短任务,可以动态回收空闲线程,减少资源占用。
  4. ScheduledThreadPool(预定线程池

    • 特点:用于延迟执行或定期执行任务。
    • 用途:适用于需要定时执行任务的场景,如调度任务、周期性执行任务等。
  5. WorkStealingPool(工作窃取线程池

    • 特点:利用ForkJoinPool实现,能够将一个大任务分解为多个小任务,并且可以跨线程执行这些小任务。
    • 用途:适用于可以并行计算的任务,特别是可以分解为多个小任务的场景,如并行计算、大数据处理等。
  6. ForkJoinPool(叉Join线程池

    • 特点:专为并行计算设计,利用工作窃取算法,能够高效地处理可以分解为多个小任务的场景。
    • 用途:适用于可以并行处理的计算密集型任务,如科学计算、大数据处理等。
  7. 自定义线程池

    • 特点:通过直接实例化ThreadPoolExecutor类来创建,可以自定义线程池的各项参数,如核心线程数、最大线程数、工作队列、线程工厂、拒绝策略等。
    • 用途:适用于需要特定线程池参数的场景,可以根据实际需求灵活配置线程池
  • 上述的线程池ScheduledThreadPoolForkJoinPool 这两个线程池以独立类的形式存在, 剩下的线程池都是以ThreadPoolExecutor为基础, 通过不同的参数衍生出来的线程池

以ScheduledThreadPool为例的类继承关系

ScheduledThreadPoolExecutor

每种线程池都有其特定的使用场景,选择合适的线程池类型可以提高程序的性能和资源利用率。在实际应用中,应根据任务的特性和需求来选择最合适的线程池类型。

拒绝策略有哪些?

在Java的线程池中,当任务太多,无法被线程池及时处理时,就需要使用拒绝策略(Rejected Execution Handler)。以下是Java线程池中常用的拒绝策略:

  1. AbortPolicy

    • 默认拒绝策略。当任务被拒绝时,会抛出RejectedExecutionException异常。
  2. CallerRunsPolicy

    • 该策略将任务回退到调用者,即由调用者线程来运行任务。如果调用者线程忙,则任务会等待,但这至少可以保证任务不会丢失。
  3. DiscardPolicy

    • 该策略默默地丢弃无法处理的任务,不抛出异常也不记录日志。
  4. DiscardOldestPolicy

    • 该策略丢弃工作队列中最早的未处理任务,并尝试再次提交当前任务。

除了上述四种预定义的拒绝策略外,你还可以自定义拒绝策略,通过实现RejectedExecutionHandler接口来定义自己的处理逻辑。

示例代码,自定义拒绝策略:

java">public class MyRejectedExecutionHandler implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {// 自定义处理逻辑// 例如,记录日志、将任务添加到其他队列等System.out.println("Task " + r.toString() + " rejected from " + executor.toString());}
}

然后在创建线程池时,可以设置自定义的拒绝策略:

java">ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,timeUnit,workQueue,new MyRejectedExecutionHandler()
);

选择合适的拒绝策略对于确保线程池在高负载情况下的正确行为至关重要。开发者应根据应用的具体需求和异常处理策略来选择或实现拒绝策略。

使用范例

下面是一个使用Java线程池的示例,其中包含了线程池的创建、提交任务、关闭线程池等操作。

固定大小的线程池

java">import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class FixedThreadPoolExample {public static void main(String[] args) {// 创建一个固定大小的线程池ExecutorService executor = Executors.newFixedThreadPool(4);// 提交任务给线程池执行for (int i = 0; i < 8; i++) {int taskNumber = i;executor.submit(() -> {System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 关闭线程池// shutdown() 方法会平滑地关闭线程池,不再接受新任务,但会处理已提交的任务executor.shutdown();// 等待所有任务完成,或者超时try {// 等待线程池中所有任务完成executor.awaitTermination(30, TimeUnit.SECONDS);} catch (InterruptedException e) {System.out.println("Tasks interrupted");}System.out.println("Finished all tasks");}
}

单线程执行器

java">import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SingleThreadExecutorExample {public static void main(String[] args) {// 创建一个单线程执行器ExecutorService executor = Executors.newSingleThreadExecutor();// 提交任务给单线程执行器执行for (int i = 0; i < 5; i++) {int taskNumber = i;executor.submit(() -> {System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());});}// 关闭单线程执行器executor.shutdown();}
}

可缓存的线程池

java">import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class CachedThreadPoolExample {public static void main(String[] args) {// 创建一个可缓存的线程池ExecutorService executor = Executors.newCachedThreadPool();// 提交任务给线程池执行for (int i = 0; i < 10; i++) {int taskNumber = i;executor.submit(() -> {System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 关闭线程池executor.shutdown();}
}

定时及周期性任务的线程池

java">import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolExample {public static void main(String[] args) {// 创建一个定时及周期性任务的线程池ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);// 定时任务executor.schedule(() -> {System.out.println("Scheduled task executed on thread " + Thread.currentThread().getName());}, 10, TimeUnit.SECONDS);// 周期性任务executor.scheduleAtFixedRate(() -> {System.out.println("Periodic task executed on thread " + Thread.currentThread().getName());}, 0, 5, TimeUnit.SECONDS);// 关闭线程池(注意:关闭前需要等待一段时间,以确保任务执行完成)Runtime.getRuntime().addShutdownHook(new Thread(() -> {System.out.println("Shutting down executor");executor.shutdown();try {if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {executor.shutdownNow();}} catch (InterruptedException e) {executor.shutdownNow();}}));}
}

这些示例展示了如何使用Java中的不同类型线程池来执行任务。在实际应用中,你可以根据任务的特性和需求选择合适的线程池类型。记得在不再需要线程池时正确关闭它,以释放系统资源。


http://www.ppmy.cn/devtools/126714.html

相关文章

SAP SD学习笔记- 豆知识 - SAP中的英文 - SD中英文,日语,中文

SD的中部分中日英文对照。先收集&#xff0c;等以后再整理。 1&#xff0c;販売管理&#xff08;销售管理&#xff09; 日本語英語中国語受注伝票sales order销售订单出荷伝票delivery order交货订单ピッキングリストpicking list领货清单シップメント伝票shipment document发…

十一、结构型(享元模式)

享元模式&#xff08;Flyweight Pattern&#xff09; 概念 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过共享大量细粒度对象来减少内存占用。享元模式将内外部状态分离&#xff0c;内部状态可以共享&#xff0c;外部状态由客户端…

数据字典是什么?和数据库、数据仓库有什么关系?

一、数据字典的定义及作用 数据字典是一种对数据的定义和描述的集合&#xff0c;它包含了数据的名称、类型、长度、取值范围、业务含义、数据来源等详细信息。 数据字典的主要作用如下&#xff1a; 1. 对于数据开发者来说&#xff0c;数据字典包含了关于数据结构和内容的清晰…

FreeRTOS - 队列

在学习FreeRTOS过程中&#xff0c;结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源&#xff0c;整理了该篇文章。如有内容理解不正确之处&#xff0c;欢迎大家指出&#xff0c;共同进步。 1. 队列 1.1 队列基本概念 队列(queue)可以用…

UNIX网络编程-简介

概述 要编写通过计算机网络通信的程序&#xff0c;首先要确定这些程序相互通信所用的协议&#xff08;protocal&#xff09;。 大多数网络应用划分为客户端&#xff08;client&#xff09;和服务器&#xff08;server&#xff09;。在设计网络应用时&#xff0c;确定总是由客户…

线性代数 行列式

一、行列式 1、定义 一个数学概念&#xff0c;主要用于 线性代数中&#xff0c;它是一个可以从方阵&#xff08;即行数和列数相等的矩阵&#xff09;形成的一个标量&#xff08;即一个单一的数值&#xff09; 2、二阶行列式 &#xff0c;像这样将一个式子收缩称为一个 2*2 的…

2d 数字人实时语音聊天对话使用案例;支持asr、llm、tts实时语音交互

参考: https://github.com/lyz1810/live2dSpeek 下载live2dSpeek项目 ## 下载live2dSpeek git clone https://github.com/lyz1810/live2dSpeek cd live2dSpeek-main ## 运行live2dSpeek npm install -g http-server http-server .更改新的index.html页面 index.html

Android从上帝视角来看PackageManagerService

戳蓝字“牛晓伟”关注我哦&#xff01; 用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章&#xff0c;技术文章也可以有温度。 前言 阅读该篇之前&#xff0c;建议先阅读下面的系列文章&#xff1a; Android深入理解包管理–PackageManagerService和它的“小伙伴…