• 周一. 10 月 7th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

线程池的那些事其一

admin

11 月 28, 2021

 

一,什么是线程池?

   简单来说,管理线程的池子。帮我们重复管理线程,避免创建大量的线程增加开销。

二,为什么用线程池?

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

三,什么时候用线程池?

  1. 单个任务处理时间比较短

  2. 需要处理的任务数量很大

四,解读线程池的原理

  A,七个核心参数

  1,corePoolSize,核心线程数量

  默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中

  2,maximumPoolSize,线程数最大值

  3,keepLiveTime,多长时间被回收

  默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

  4,timeUnit,设置keepLiveTime的时间单位,共7种单位:d, h, m, s, ms, mis, ns 

  5,workQueue,存放提交至线程池但未被执行的任务

  ArrayBlockingQueue(有界队列),是一个用数组实现的有界阻塞队列,按FIFO排序量。

  LinkedBlockingQueue(可设置容量队列),基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列

  DelayQueue(延迟队列),是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。

  PriorityBlockingQueue(优先级队列),是具有优先级的无界阻塞队列;

  SynchronousQueue(同步队列),一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。

  6,threadFactory,创建线程的工厂

  用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

使用开源框架guava提供的ThreadFactoryBuilder可以快速给线程池里的线程设置有意义的名字,code: new ThreadFactoryBuilder().setNameFormat(“Hello-Engine”).build();

  7,rejectedExecutionHandler,拒绝策略设置

  CallerRunsPolicy:只要线程池没关闭,就用调用者所在线程来运行任务

  AbortPolicy:直接抛RejectedExecutionException 异常

  DiscardPolicy:不处理,直接扔了

  DiscardOldestPolicy:把队列里待最久的那个任务扔了,并执行当前任务

  支持实现自己的 RejectedExecutionHandler 接口自定义策略,如记录日志类。

  B,线程池的状态

RUNNING

  • 该状态的线程池会接收新任务,并处理阻塞队列中的任务;

  • 调用线程池的shutdown()方法,可以切换到SHUTDOWN状态;

  • 调用线程池的shutdownNow()方法,可以切换到STOP状态;

SHUTDOWN

  • 该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;

  • 队列为空,并且线程池中执行的任务也为空,进入TIDYING状态;

STOP

  • 该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;

  • 线程池中执行的任务为空,进入TIDYING状态;

TIDYING

  • 该状态表明所有的任务已经运行终止,记录的任务数量为0。

  • terminated()执行完毕,进入TERMINATED状态

TERMINATED

  • 该状态表示线程池彻底终止

 

  C,任务执行流程

  1,execute()方法

  a,主要流程图如下:

  b,ThreadPoolExecutor执行流程

  • 提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。

  • 如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。

  • 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。

  • 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。

 

   c,源码解读

 2,submit()方法

ps: 线程池抛异常了,如何处理?

 

五,怎样使用线程池?

A,Executors常用方法及问题

 1,常用方法四种:

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

 

工作机制:

 

适用场景:用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。

  •  

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

 

工作机制:

 

适用场景:用于串行执行任务的场景,一个任务一个任务地执行。

  •  

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

 

工作机制:

       

 

适用场景:用于并发执行大量短期的小任务。

  •  

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

 

工作机制:

 

  • 适用场景: 周期性执行任务的场景,需要限制线程数量的场景

 

 2,使用Executors的问题, 来自阿里Java规范的建议:

       

B,自定义线程池

1,合理配置线程池

     通常我们是需要根据这批任务执行的性质来确定的。

  • IO 密集型任务:由于线程并不是一直在运行,所以可以尽可能的多配置线程,比如 CPU 个数 * 2

  • CPU 密集型任务(大量复杂的运算)应当分配较少的线程,比如 CPU 个数相当的大小。

     当然这些都是经验值,最好的方式还是根据实际情况测试得出最佳配置。

2,推荐使用Guava提供的ThreadFactoryBuilder来创建线程池
     

 

 

 

 

发表回复