java有哪些常见的线程池,java 使用线程池

  java有哪些常见的线程池,java 使用线程池

  00-1010简介参数解释如何创建线程池拒绝策略摘要。

  00-1010线程池执行器是实现ExecutorService接口的线程池。ExecutorService是一个主要用于处理多线程任务的接口。通常,由Executors工厂类创建用法更简单。

  线程池主要解决两个不同的问题:

  当执行大量异步任务时,为了提高性能,通常会降低每个任务的调用开销。它提供了一系列多线程任务的管理方法,便于合理分配资源和处理多任务执行过程中的一些异常情况。每个ThreadPoolExecutor还维护一些基本的统计数据。例如:已完成的任务数、当前获取的线程数等。

  00-1010线程池执行器提供了几个核心参数,方便开发者根据具体场景合理分配线程资源。

  CorePoolSize:核心线程的数量,线程池创建时已经初始化的n个核心线程,即使线程空闲,也会保留在线程池中而不被销毁,除非调用线程池的方法设置Java . util . concurrent . threadpooleexecutor # allowcorethreadtimeout(true)(允许核心线程在超时时被销毁)。MaximumPoolSize:线程池中允许的最大线程数。KeepAliveTime:当线程数大于核心线程数时,任务执行完毕后,冗余线程等待新任务的最大等待时间。Unit:TimeUnit类型,是keepAliveTime冗余线程的最大空闲时间单位。工作队列:您必须指定一个阻塞队列。当线程池执行execute方法时,新任务将被保存在这个队列中等待执行。ThreadFactory:创建线程的工厂。默认情况下,它使用Executors.defaultThreadFactory()来创建线程。处理人:拒绝政策。当最大数量的线程已满且队列已满时,线程池将触发拒绝策略来拒绝新的传入任务。具体的处理方案后面会详细分析(默认使用Java . util . concurrent . threadpooleexecutor . abort策略直接抛出异常拒绝)。注意:如果maximumPoolSize大于corePoolSize,那么当阻塞队列工作队列已满时,额外的线程数只会创建除核心线程之外的线程来执行任务。如果我们设置的阻塞队列是一个无界队列(默认大小是整数。MAX_VALUE),队列永远不会满,也不会创建额外的线程来工作。一般情况下,如果任务数量足够多,在队列大小达到整数之前就会发生内存溢出。MAX_VALUE。Executors线程池工厂中的newFixedThreadPool()和newSingleThreadExecutor()方法使用了无界限队列LinkedBlockingQueue,这样可以防止内存溢出。在日常开发过程中,一般不建议直接使用执行器创建线程池。

  00-1010如上所述,我们可以使用Executors工厂直接创建线程池,但是Executors创建的线程池是不可控的。我们还是要做好分析,根据自己的业务定制一个线程池。

  下面是线程池创建的一个例子:

  @ Slf4j @ configuration public class ThreadPoolConfig { @ Value( $ { thread pool . corePoolSize :8 } )private int corePoolSize;@ Value( $ { thread pool . maximumPoolSize :16 } )private int maximumPoolSize;@ Value( $ { thread pool . keepAliveTime :60 } )private int keepAliveTime;@ Value( $ { thread pool . queueSize :99999 } )private int queueSize

  ; @Bean public ThreadPoolExecutor testExecutor() { LinkedBlockingQueue queue = new LinkedBlockingQueue(queueSize); return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, queue, getThreadFactory(), getRejectedExecutionHandler()); } /** * 自定义线程池创建线程工厂,用于线程池创建线程的工厂 * @return */ private ThreadFactory getThreadFactory() { return new ThreadFactory() { @Override public Thread newThread(Runnable r) { log.info("===> Create new thread ..."); return new Thread(r); } }; } /** * 自定义拒绝策略,继续往队列里添加任务进入等待 * @return */ private RejectedExecutionHandler getRejectedExecutionHandler() { return new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 继续往队列里添加任务,这里只是一个案例,这种方式并不友好,会抛出队列已满的异常 log.info("===> Handler runnable ......"); executor.getQueue().add(r); } }; }}application.properties配置文件

  

threadPool: corePoolSize: 8 maximumPoolSize: 16 keepAliveTime: 60 # 为方便测试这里我们配置队列数小一点 queueSize: 99
由以上的线程池配置,我们写一个demo测试一下:

  

  截取部分运行日志:

  红框我们可以看到执行到了线程创建工厂部分代码块蓝色框日志我们可以看到largestPoolSize=11,这是由于我们配置的maximumPoolSize=16 > corePoolSize=8,我们demo执行的是110个任务并发,队列大小是99,由此分析得出(需要额外出创建线程数 = 并发任务总数110 - 核心线程数8 - 队列大小99 = 3),所以线程池在队列已满时会多创建3个线程用于执行任务,在达到keepAliveTime配置的最大空闲时间后这3个线程即会自动销毁。

  

注:可能有的同学会想线程池使用后需要销毁吗?在这里补充一下,如果我们是作为局部变量创建出来的线程池(如:在执行的方法内使用Executors.newFixedThreadPool(10)创建临时的线程池),这种情况我们用完就必须将它立即销毁,否则主线程就会一直处于运行状态。如果是全局配置的线程池,那么就是为整个系统中诸多业务提供使用的,这种就不需要对线程池做销毁,因为一旦销毁了其他的任务就无法继续使用该线程池执行任务。

  

销毁线程池主要有两种方式:shutdown():此方法对线程池做销毁,线程池会优先将剩余未完成的任务执行完才会执行销毁任务。shutdownNow():此方法会对线程池做立即销毁,无论线程池中的任务是否执行完成。

  

拒绝策略

通常我们在配置好有限队列大小后,就会有可能出现队列占满的情况,这时候我们的拒绝策略就会起到作用,接下来我们就来分析一下RejectedExecutionHandler接口具体有哪一些实现方式:

  AbortPolicy:线程池的默认拒绝策略,在JDK提供的ThreadPoolExecutor线程池中有一个默认线程池变量private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();作为默认拒绝策略,查看如下图源码可知它就是直接抛出RejectedExecutionException异常,并且会直接丢弃当前任务,如果担心异常影响后续任务执行开发人员需自行捕获异常处理

  CallerRunsPolicy:只要在当线程池未被销毁的情况下,不丢弃任务直接使用主线程(调用线程池执行的线程)执行该任务。因为该策略是由主线程直接执行任务的,所以不建议在并发度高的情况下使用,建议在并发度较低且任务不允许失败的情况下才使用此策略

  DiscardPolicy:直接丢弃当前任务,不做任何处理。直接丢弃任务的情况下,开发人员也无法排查到哪些任务被丢弃掉,一般不建议使用,除非是无关紧要的任务即使丢弃也无所谓的。

  DiscardOldestPolicy:在线程池未被销毁的情况下,丢弃最早进入队列的一个任务(即最久未执行的任务),然后再重新将此任务加入线程池,在此策略下需注意被丢弃的任务的重要性,如果任务不重要可直接丢弃。

  自定义策略:在以上JDK提供的四种默认拒绝策略之外,我们还可以通过自定义的方式来处理被拒绝的任务。如果担心任务被拒绝或者被丢弃造成不可预估的问题,在时效性没有太大要求的情况下我们可以先将任务内容转换成数据入库做好日志记录,后续可以使用定时任务或者通过MQ消息延迟处理。由以上的线程池配置Demo中的拒绝策略改造伪代码如下:

private RejectedExecutionHandler getRejectedExecutionHandler() { return new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 伪代码 log.info("===> 可根据任务的重要性区分对待,将任务做转换入库延迟处理 ......"); } };}

  

总结

线程池是为了充分利用CPU资源,在合理分批使用的情况下能够极大的提高我们程序的性能,以上的参数配置仅作为参考,并没有一个标准的依据,只能在实际开发过程中开发人员自行多做一些测试来判断参数如何配置更加合理。

  在拒绝策略配置方面,如果被拒绝的任务相对紧急且重要不可丢弃的情况下,此类任务可独立做一个线程池处理保证任务不丢失,程序只能慢慢优化变得越来越好,不可能有完美的程序即保证高性能又保证安全可靠。

  到此这篇关于Java线程池ThreadPoolExecutor的文章就介绍到这了,更多相关Java线程池ThreadPoolExecutor内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: