线程池高级理论总结

简介: 线程池高级理论总结

1 线程池工作流程概述


说明:


  • 还未执行任何任务的时候,核心线程是0个,他是懒加载;
  • 当一个任务通过submit 或者 execute方法提交到线程池的时候,如果当前池中线程数(包括闲置线程)小于coolPoolSize,则创建一个新的线程执行该任务;
  • 如果当前线程池中线程数已经达到coolPoolSize,则将任务放入等待队列;
  • 如果任务队列已满,则任务无法入队列,此时如果当前线程池中线程数小于maxPoolSize,则创建一个临时线程(非核心线程)执行该任务;
  • 如果当前池中线程数已经等于maxPoolSize,此时无法执行该任务,对于新的任务会根据拒绝执行策略处理;


注意:


当池中线程数大于coolPoolSize,超过keepAliveTime时间的闲置线程会被回收掉。回收的是非核心线程,核心线程一般是不会回收的。如果设置allowCoreThreadTimeOut(true),则核心线程在闲置keepAliveTime时间后也会被回收。


2 线程池拒绝策略


2.1.什么时候会触发拒绝策略?


  • 当线程池调用 shutdown() 等方法关闭线程池后,如果再向线程池内提交任务,就会遭到拒绝;
  • 当线程达到最大线程数,且无空闲线程,同时任务队列已经满;


2.2.拒绝策略类型有哪些


  • 线程池为我们提供了4种拒绝策略:


AbortPolicy(抛出异常中断程序执行)


这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略(默认)。

说白了不仅不处理当前任务,并且还抛出异常,中断当前任务的执行;


DiscardPolicy(任务丢弃不抛出异常)


当有新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。


DiscardOldestPolicy(丢弃存活时长最长的任务)


丢弃任务队列中的头结点,通常是存活时间最长的任务,它也存在一定的数据丢失风险。


CallerRunsPolicy(推荐)


第四种拒绝策略是 ,相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。

任务线程满了后,改策略可将执行的人为交换给主线程执行,这个过程相当于一个正反馈,此时如果主线程能处理,则处理,如果也不能处理,也就以为这当前服务不能接收新的任务了;

主线程处理任务期间,可以为线程池腾出时间,如果此时有新的空闲线程,那么继续协助主线程处理任务;


2.3.如何自定义拒绝策略?


通过实现 RejectedExecutionHandler 接口来自定义任务拒绝策略;

例如:

/**
 * 自定义线程拒绝策略
 * @return
 */
@Bean
public RejectedExecutionHandler rejectedExecutionHandler(){
    RejectedExecutionHandler errorHandler = new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
            //TODO 可自定义Runable实现类,传入参数,做到不同任务,不同处理
            log.info("股票任务出现异常:发送邮件");
        }
    };
    return errorHandler;
} 


3 结论


结论:

  • 线程池对象初始化时采用延迟加载的方式构建核心线程对象,因为线程对象构建有资源开销;
  • 当线程池内的线程数未达到核心线程数时,此时哪怕线程是空闲的,不会复用线程,而是最大努力构建新的线程,直到达到核心线程数为止;


结论:当核心线程数使用完后,多余的任务优先压入任务队列;

  • 当核心线程数被占用,且有新的任务时,优先将新的任务压入队列;
  • 当核心线程数已满,且阻塞队列如果未填满,会持续填入;
  • 当核心线程存在空闲时,会主动去阻塞队列下领取新的任务处理;


结论:

  • 当核心线程数已被使用,且任务队列已满,优先开启临时线程处理任务;
  • 线程执行流程:1.先扩容5个核心线程处理任务 2.将多余的10个任务压入队列 3.剩下的构建临时线程处理任务(最大线程数=临时线程数+核心线程数)
  • 临时线程如果空闲,且达到keepAliveSenconds指定的最大存活时间,则会被淘汰,直到达到核心线程数为止;


默认采用AbortPolicy策略,直接中断程序执行

结论:


  • 什么时候触发任务的拒绝策略?

1.任务队列已满,且没有空闲的线程(线程已经达到最大线程数),则会触发拒绝策略;

2.线程池对象sutdown关闭拒绝服务时,也会触发拒绝策略;

3.拒绝策略:

jdk给我们提供了4种;

AbortPolicy 抛出异常,终止程序允许

DiscardPolicy 丢弃新的任务,不抛出异常

DiscardOldestPolicy 丢弃旧的任务,不抛出异常

CallsRunerPolicy 委托主线程执行任务 不抛出异常


5、线程池参数设置原则


5.1 如何为线程池设置合适的线程参数?


目前根据一些开源框架,设置多少个线程数量通常是根据应用的类型**:I/O 密集型、CPU 密集型。**


  • I/O密集型


I/O密集型的场景在开发中比较常见,比如像 MySQL数据库读写、文件的读写、网络通信等任务,这类任务不会 特别消耗CPU资源,但是IO操作比较耗时,会占用比较多时间;

IO密集型通常设置为 2n+1,其中 n 为 CPU 核数;

说白了,对于i/o密集型的场景,不太占用cpu资源,所以并发的任务数大于cpu的核数,这样的话能更加充分的利用CPU资源;


  • CPU密集型


CPU密集型的场景,比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,这些场景大部分都是纯 CPU计算;

CPU密集型通常设置为n+1,这样也可避免多线程环境下CPU资源挣钱带来上下文频繁切换的开销;


5.2 如何获取当前服务器的cpu核数?

int cors = Runtime.getRuntime().availableProcessors();


5.3 无界队列问题


实际运行中,我们一般会设置线程池的阻塞队列长度,如果不设置,则采用默认值:

private int corePoolSize = 1;
private int maxPoolSize = Integer.MAX_VALUE;
private int keepAliveSeconds = 60;
private int queueCapacity = Integer.MAX_VALUE;

在这个过程中,如果设置或者使用不当,容易造成内存溢出问题,同时如果设置了无界队列,那么线程池的最大线程数也就失去了意义;

所以企业开发中会命令禁止使用默认的队列长度

相关文章
|
6月前
|
Java 调度
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
|
2月前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
135 29
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
3月前
|
缓存 监控 Java
Java性能优化:从单线程执行到线程池管理的进阶实践
在Java开发中,随着应用规模的不断扩大和用户量的持续增长,性能优化成为了一个不可忽视的重要课题。特别是在处理大量并发请求或执行耗时任务时,单线程执行模式往往难以满足需求,这时线程池的概念便应运而生。本文将从应用场景举例出发,探讨Java线程池的使用,并通过具体案例和核心代码展示其在实际问题解决中的强大作用。
|
4月前
|
设计模式 并行计算 安全
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
49 0
|
4月前
|
存储 监控 Java
理解线程池的原理与最佳实践
理解线程池的原理与最佳实践
|
6月前
|
监控 Java 调度
Java并发编程:线程池的原理与实践
【5月更文挑战第30天】 在现代软件开发中,尤其是Java应用中,并发编程是一个不可忽视的领域。线程池作为提升应用性能和资源利用率的关键技术之一,其正确使用和优化对系统稳定性和效率至关重要。本文将深入探讨线程池的核心原理、常见类型以及在实际开发中的使用案例,旨在帮助开发者更好地理解和运用线程池技术,构建高性能的Java应用程序。
|
6月前
|
Java 开发者
Java并发编程:理解线程池的工作原理与实践应用
【5月更文挑战第29天】在Java并发编程中,线程池是一种管理线程资源的重要工具。通过深入探讨线程池的工作原理和实践应用,本文将帮助开发者更好地理解和使用线程池,提高系统性能和稳定性。
|
6月前
|
消息中间件 缓存 Java
【多线程学习】深入探究定时器的重点和应用场景
【多线程学习】深入探究定时器的重点和应用场景
117 1
|
6月前
|
安全 Java 调度
【C/C++ 线程池设计思路 】设计与实现支持优先级任务的C++线程池 简要介绍
【C/C++ 线程池设计思路 】设计与实现支持优先级任务的C++线程池 简要介绍
185 2
|
Java 数据库连接 调度
线程池概念简单入门
线程池概念简单入门