通俗易懂的线程池解析,结合并发实战
Quick Start
构造参数解析
1 | public ThreadPoolExecutor(int corePoolSize // 核心线程数, |
拒绝策略是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。
BlockingQueue队列:
队列子类太多,大致过一遍就可以了,但是要记住基于链表的队列是有容量危险的,容易造成任务堆积和下方不允许使用Executors显示创建时一个原因。
两种创建方式
手动创建
1 | ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor |
自动创建
juc包的executors类有提供默认的ThreadPoolExecutor类常用的几种实现
1 | public static ExecutorService newFixedThreadPool(int nThreads) { |
阿里规范不允许显示使用
执行过程
其执行过程如下:
- 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
直接看文字有点抽象,直接看下面的demo代码,通过不同的线程池参数调节来观察
demo1:
核心线程数1,最大线程数1,linkedBlockingQueue
打印的线程name
可以看到都是同一个线程在处理,所以处理逻辑,第一个任务进来,创建一个核心线程thread-1,因为最大核心线程数是1,所以后面的任务放到了linkedBlockingQueue里等待thread-1处理完成之后消费,一个个排队。
demo2
linkedBlockingQueue容量为1,运行后
可以看到只打印了线程执行就抛错了,所以逻辑应该是,第一个任务进来小于核心线程数,创建并执行。第二个任务进来,因为核心线程数为1,所以进入了linkedBlockingQueue。第三个任务进来,队列满了,最大线程数为1,不能创建了,只能抛出指定的拒绝策略。
demo3
加大核心线程数,执行结果:
与demo1一样,因为队列是无界的,所以触发不了队列满了之后创建线程。
demo4
核心线程数1 + 最大线程数10 + 一个容量为100的队列 ,执行结果
刚开始第一个任务进来,创建一个核心线程thread-1,后续的任务进入容量100的队列,101个任务开始因为最大线程数是10,开始创建thread2-thread10,同时这个10个线程开始并发的操作数据库修改user,同时user加了乐观锁导致修改失败,最后最大线程数10 + 队列容量100 = 110个满了之后,触发拒绝策略,所以可以看到并发开始了打印110次。