线程池

一、线程池构建

有参构造:

1
2
3
4
5
6
7
8
9
10
/*
int corePoolSize - 保留在池中的线程数,即使它们是空闲的,除非设置allowCoreThreadTimeOut
int maximumPoolSize – 池中允许的最大线程数
long keepAliveTime – 当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最长时间。
TimeUnit unit – keepAliveTime参数的时间单位
BlockingQueue<Runnable> workQueue – 用于在执行任务之前保存任务的队列。此队列将仅保存由execute方法提交的Runnable任务。
ThreadFactory threadFactory – 执行器创建新线程时使用的工厂(可设置线程前缀)
RejectedExecutionHandler handler – 由于达到线程边界和队列容量而阻塞执行时使用的处理程序
*/

image-20220908104905917

1
2
3
4
//自定义线程池例子:
ThreadPoolExecutor executor = new ThreadPoolExecutor(16, 16, 0L,
TimeUnit.MILLISECONDS, new LinkedTransferQueue(), new CustomizableThreadFactory("线程"),new ThreadPoolExecutor.AbortPolicy());
executor.execute(()->System.out.println(Thread.currentThread().getName()+"执行"));

二、常用线程池

newFixedThreadPool(固定数目线程的线程池)

image-20220908153906732

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

newCachedThreadPool(可缓存线程的线程池)

image-20220908154518504

当提交任务的速度大于处理任务的速度时,每次提交一个任务,就必然会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。由于空闲 60 秒的线程会被终止,长时间保持空闲的CachedThreadPool 不会占用任何资源)

newSingleThreadExecutor(单线程的线程池)

image-20220908154959565

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

newScheduledThreadPool(定时及周期执行的线程池)

image-20220908155341568

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

三、执行流程

image-20220908114734017

理论上:提交的任务数>(maxPoolSize+queueCapacity)时就会触发绝绝策略

四、阻塞队列

实现了BlockingQueue接口的类:

阻塞队列 描述 是否有界
ArrayBlockingQueue 基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue 基于链表结构的有界阻塞队列,此队列按 FIFO 排序元素,吞吐量通常要高于 ArrayBlockingQueue且界限默认大小为Integer.MAX_Value(2^31-1),值非常大,相当于无界
LinkedBlockingDeque 基于链表结构的双向有界阻塞队列
LinkedTransferQueue 基于链表组成的无界阻塞传输队列
SynchronousQueue 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue
DelayQueue 使用优先级队列实现的延迟无界阻塞队列

五、拒绝策略

默认拒绝策略为AbortPolicy

img

拒绝策略 描述
AbortPolicy 这种拒绝策略在拒绝任务时,会直接抛出异常 RejectedExecutionException (属于RuntimeException),让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略
DiscardPolicy 这种拒绝策略当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失
DiscardOldestPolicy 如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险
CallerRunsPolicy 当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处(1.新提交的任务不会被丢弃,这样也就不会造成业务损失。2.由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期)

六、线程调优

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目