【JUC基础】12. 线程池(一)

news/2024/12/2 21:30:17/

1、前言

我们知道多线程的使用,是为了最大限度发挥现代多核处理器的计算能力,提高系统的吞吐量和性能。但是如果不加以控制和管理,随意使用多线程,对系统性能反而会有不利的影响。线程数量和系统CPU资源是息息相关的,随意使用甚至可能会耗尽系统CPU资源和内存资源。

2、池化技术

为了应对多线程管理和控制的问题,引入池化技术。所谓池化技术,顾名思义就是造一个池子,讲需要管理的东西交给池子管理,而用完之后就放回池子。就像小孩子的玩具收纳箱,玩玩具的时候从收纳箱中拿出玩具,玩完之后一定要教小孩子将玩具放回玩具收纳箱中。池化技术通过优化资源的分配效率,从来达到性能的调优。

其实在Java编程中,池化技术不仅仅在多线程中使用这种方式,其他地方也同样用到了池化技术。如:数据连接池,对象池,内存池等等。

3、为什么要用线程池

前面基础多少讲到了为什么使用线程池的原因。这里详细说明以下几个原因,由ChatGPT来总结一下:

在多线程编程中,频繁地创建和销毁线程是一项昂贵的操作。因此,使用线程池来管理线程的创建、复用和销毁是一种有效的方式。 以下是几个原因解释为什么要使用线程池:

  1. 降低资源消耗:线程的创建和销毁需要消耗系统资源,如内存和CPU。使用线程池可以重用线程,避免频繁创建和销毁线程,从而降低了资源消耗。
  2. 提高系统响应性:线程池能够提高系统的并发能力和响应性。通过合理地配置线程池的大小,可以同时执行多个任务,提高系统的吞吐量和响应时间。
  3. 任务调度和线程复用:线程池可以管理和调度任务的执行。它维护一组线程,可以根据任务的到达顺序和优先级来选择合适的线程执行任务,避免任务争抢和冲突。同时,线程池中的线程可以被重复利用来执行多个任务,避免了频繁创建线程的开销。
  4. 控制并发线程数量:通过设置线程池的大小和任务队列的容量,可以限制并发执行的线程数量,防止系统资源被过度占用,从而提高系统的稳定性和可靠性。
  5. 简化线程编程:使用线程池可以将任务的提交和执行解耦,简化了线程编程的复杂性。开发人员只需关注任务的实现和提交,无需手动创建和管理线程,从而降低了出错的概率。

3.1、线程池优点

线程池优点很明显,上面提到为什么要使用线程池的几个原因就是对应的优点,这里不赘述。

3.2、线程池缺点

线程池的缺点也很明显:

  1. 资源占用:线程池本身会占用一定的系统资源,包括内存和CPU。如果线程池的大小设置不合理,可能会导致资源浪费或不足的问题。
  2. 线程泄露:如果没有正确地关闭线程池,或者任务执行过程中出现异常导致线程无法正常释放,可能会导致线程泄露,进而影响系统性能。
  3. 需要合理配置:线程池的性能和效果受到配置参数的影响,需要根据具体场景合理配置线程池的大小、任务队列的容量等参数,否则可能会影响系统的性能和响应性。
  4. 难以处理长时间任务:线程池主要适用于短时间的任务处理,如果任务执行时间过长,可能会导致线程池中的线程被长时间占用,影响其他任务的执行。

4、如何使用线程池

最简单的线程池使用方法:

public class ThreadPoolTest {public static void main(String[] args) {// 创建一个固定大小的线程池,大小为3ExecutorService executorService = Executors.newFixedThreadPool(3);// 提交任务给线程池执行for (int i = 0; i < 10; i++) {// 执行提交任务executorService.execute(() -> {// ......});}// 关闭线程池executorService.shutdown();}
}
  1. 通过Executors.newFixedThreadPool(3)方法创建了一个线程池,该线程池固定线程数量为3;
  2. 使用executorService.execute()方法执行向线程池内提交的线程任务;
  3. 执行完后,通过executorService.shutdown();关闭线程池资源;

通过简单的线程池使用方式,我们就完成了基本的线程池操作。线程池会自动管理线程的创建和销毁,以及任务的调度和执行,帮我们简化了多线程编程的复杂性。

5、JUC线程池

5.1、Executor

Executor 线程池顶级接口,类似一个线程池工厂。接口中只有一个execute()方法,接收Runnable类型。注意这里返回值类型是void。

 

5.2、ExecutorService

ExecutorService继承自Executor接口,添加了关闭线程池以及等待中断等方法。同时添加了submit来提交线程任务,除了接收Runnable以外,还可以接收Callable类型,也增加了返回值。

5.3、AbstractExecutorService

AbstractExecutorService是实现ExecutorService接口的抽象类。默认实现了个别如submit方法等。

5.4、ScheduledExecutorService

该类是为了实现带有定时器功能的线程池。ScheduledExecutorService也是一个接口。包含了定时和延迟处理的方法。

5.5、ThreadPoolExecutor方法参数

ThreadPoolExecutor重点看这个类。ThreadPoolExecutor是JUC中提供的默认线程池实现类。提供了丰富的配置选项和线程池管理功能。

提供了4个可选配置的构造函数:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

我们重点关注其中的几个参数:corePoolSize,maximumPoolSize,keepAliveTime,workQueue,threadFactory,handler。

5.5.1、corePoolSize

核心线程数。指线程池中始终保持的线程数量,就算他们处于空闲状态,也不会被销毁。而一直存活的最小线程数量。

5.5.2、maximumPoolSize

最大线程数。指线程池中允许的最大线程数量。当maximumPoolSize的数量大于corePoolSize时,多的那部分空闲状态下的线程,会再超过一定时间后被销毁,只保留corePoolSize的核心线程数。

5.5.3、keepAliveTime

非核心线程保持空闲的时间。如果超过这个时间,多的那部分空闲状态下的线程就会被销毁。可以通过unit设置时间单位。

5.5.4、BlockingQueue

任务队列,阻塞队列。当前并发执行的线程数与系统资源有关。当你设置了大于当前系统可负载的线程数量时,多的那部分自然要进行等待,从而进入等待队列。

当线程池中线程数量达到corePoolSize时,且都处于运行状态,这时候后续提交的线程任务会进入到缓存阻塞队列中,等待执行。这个缓存阻塞队列也就是workQueue。

JUC中提供的BlockingQueue有以下几种:

  1. ArrayBlockingQueue:由数组实现的有界阻塞队列。需要指定队列的容量大小。当队列已满时,添加任务的操作将被阻塞,直到队列中有空位。ArrayBlockingQueue适用于固定大小的线程池,可以控制线程池中的最大任务数。
  2. LinkedBlockingQueue:由链表实现的可选有界或无界阻塞队列。如果创建LinkedBlockingQueue时没有指定容量大小,那么它将是一个无界队列,可以无限制地添加任务。如果指定了容量大小,它将成为一个有界队列。当队列已满时,添加任务的操作将被阻塞。LinkedBlockingQueue适用于任务数比较大且变化较大的场景。
  3. SynchronousQueue:一个没有缓冲区的阻塞队列。每个插入操作必须等待一个相应的删除操作,反之亦然。SynchronousQueue适用于任务直接交付给线程执行的场景,可以有效地避免任务的排队和缓冲。
  4. PriorityBlockingQueue:支持优先级排序的无界阻塞队列。元素按照比较器或元素的自然顺序进行排序。PriorityBlockingQueue适用于需要按照优先级顺序处理任务的场景。

注:当使用了无界队列后,maximumPoolSize会失效。

这些BlockingQueue的区别主要在于容量限制、阻塞特性和元素排序。根据具体的需求和场景,选择合适的BlockingQueue可以提高线程池的性能和效率。

5.5.5、threadFactory

线程工厂。用于创建线程的工厂类。可以通过设置线程工厂来自定义线程的创建方式,例如设置线程名称、线程优先级等

5.5.6、RejectedExecutionHandler

拒绝策略。用于处理无法接收的任务。当线程池已满且任务无法提交时,会触发拒绝策略来处理这些任务。

JUC提供的RejectedExecutionHandler有以下几种:

  1. AbortPolicy(默认策略):该策略会直接抛出RejectedExecutionException异常,阻止任务的提交。
  2. CallerRunsPolicy:当线程池无法接收任务时,会将任务返回给调用者执行。也就是说,由提交任务的线程来执行该任务。这样可以降低任务提交速度,但可能会影响调用线程的性能。
  3. DiscardPolicy:该策略会默默丢弃无法接收的任务,没有任何提示和异常。这可能导致任务的丢失,潜在的风险需要注意。
  4. DiscardOldestPolicy:当线程池无法接收任务时,会丢弃队列中最旧的任务,然后尝试再次提交任务。这样可以保留较新的任务,但可能会丢失一些较旧的任务。

这些拒绝策略在处理无法接收的任务时具有不同的行为,可以根据具体的需求和业务场景选择合适的策略。需要根据任务的重要性、丢失任务的风险以及业务需求来综合考虑选择合适的拒绝策略。

5.6、手动创建一个线程池

private final static ThreadPoolExecutor threadPoolExecutor;static {// 这里利用hutool提供的ThreadFactoryBuilder,创建一个线程池工厂,并配置线程名称前缀// ThreadFactory是个接口,也可以自定义实现ThreadFactory threadFactory = ThreadFactoryBuilder.create().setNamePrefix("common-thread-pool-").build();threadPoolExecutor = new ThreadPoolExecutor(// 核心线程数为7,// 通常IO密集型的可以配置为2 * cpu数量// CPU密集型的可以配置为 cpu数量 + 17,// 最大线程数量20个20,// 空闲等待时间,1分钟// 超过1分钟,多余的空闲线程会被销毁1 * 60,// 空闲等待时间,单位TimeUnit.SECONDS,// 有界等待队列,固定长度为50// 如果使用无界队列,需要考虑内存占用问题new ArrayBlockingQueue(50),// 线程工厂threadFactory,// 拒绝策略new ThreadPoolExecutor.AbortPolicy());
}

6、小结

到这里,基本交代了线程池的一些基础概念,以及关于线程池的一些基础使用。后面的章节会讲到线程池的几个实现类,以及简单的场景使用案例。

持续更新中......


http://www.ppmy.cn/news/111990.html

相关文章

震旦复印机扫描到服务器文件夹,办公室复印机怎么扫描文件(图示复印机扫描功能应用)...

释放双眼&#xff0c;带上耳机&#xff0c;听听看~&#xff01; 震旦ADC223s扫描功能&#xff0c;这个复印打印机一体机支持很多种扫描文件的文件的传输方法&#xff0c;有邮件、FTP、SMB 等&#xff0c;这里我们介绍的是通过FTP的方式传输到每台电脑的指定目录中&#xff0c;电…

震旦ADC208复印机显示“请更换显影组件K”后 “哒哒…”的异响

故障现象: 震旦ADC208复印机显示“请更换显影组件K”; 检测与维修: 在更换黑色PCDU后开机,一直处于预热状态,预热大概有15分钟后还在预热,所以就关机,重新打开,重新开机后在预热时就开始有“哒哒哒…”的响声,声音很大,很容易就能听出是齿轮打滑的声音。

WPS使用复印机的扫描功能

大家好&#xff1a;明德、笃学、躬行、善建。衷心希望大家坚守本心&#xff0c;实现中华民族伟大复兴的中国梦&#xff01; 一、文章背景 带有印章的证书往往需要复印机的扫描功能&#xff0c;才能够表现出证书的细节。如果您正在为不知道如何使用复印机的扫描功能而垂头丧气…

东芝e-STUDIO 2323AM复印机双面打印怎么设置

东芝e-STUDIO 2323AM复印机&#xff0c;想要进行双面打印&#xff0c;到底应该如何设置呢&#xff1f;自动双面的话又该如何操作呢&#xff1f;下面就简单讲一下&#xff0c;分享给大家。 1、点击屏幕左下角的“开始”菜单&#xff0c;选择“打印机和传真”&#xff1b; 2、在打…

打印复印机一直提示稿台上留有原稿怎么办?

使用打印机&#xff0c;可能会遇到这种情况&#xff0c;A复印打印一直提示稿台上面留有原稿&#xff0c;但使用稿台正常。当遇到这种故障问题的时候&#xff0c;我们应该怎么办呢&#xff1f;我们应该怎么处理呢&#xff1f;下面和我一起来看看。 首先&#xff0c;我们可以将送…

夏普MX-M2658N复印机显示请放入载体组件

故障描述: 一台夏普MX-M2658N复印机一开机就显示请放入载体组件,重新再次开机有可能不显示但是复印或打印的时候一定会卡纸,卡纸有时候卡在硒鼓附近或者加热组件的位置; 故障处理: 1、碳粉质量差; 2

东芝复印机2505AC错误CD40请求维修

东芝复印机2505AC错误CD40请求维修 啥问题?其实就提示更换废粉盒&#xff0c;某些官方意思是直接清零计数器那我们来一遍 1.同时按住开始按键与功能清楚按键开机 2.输入密码密码#1048# 3.进去以后选择08 4.点下一页&#xff0c;然后右上角有个C开头的英文字母&#xff0c;点…

速印机和复印机的区别是什么?

速印机是通过数字扫描&#xff0c;热敏制版成像的方式进行工作&#xff0c;从而实现高清晰的印刷质量的印刷设备。它的印刷速度在100张/分钟以上&#xff0c;同时它还具有对原稿缩放印刷、拼接印刷、自动分纸控制等多种功能&#xff0c;绝大多数的机型还可以支持电脑打印直接输…