解释说明:
Executor 是线程池最顶层的接口,在 Executor 中只有一个 execute 方法,用于执行任务。至于线程的创建、调度等细节由子类实现。
ExecutorService 继承并拓展了 Executor,在 ExecutorService 内部提供了更全面的任务提交机制以及线程池关闭方法。
ThreadPoolExecutor 是 ExecutorService 的默认实现,所谓的线程池机制也大多封装在此类当中。
ScheduledExecutorService 继承自 ExecutorService,增加了定时任务相关方法。
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor,并实现了 ScheduledExecutorService 接口。
ForkJoinPool 是一种支持任务分解的线程池,一般要配合可分解任务接口 ForkJoinTask 来使用。
为了方便开发者可以更方便的使用线程池,JDK 中给我们提供了一个线程池的工厂类—Executors。在 Executors 中定义了多个静态方法,用来创建不同配置的线程池。常见有以下几种。(阿里Java开发手册 中已经严禁使用 Executors 来创建线程池)
1. newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按先进先出的顺序执行。
2. newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
3. newFixedThreadPool :创建一个固定数目的、可重用的线程池
4. newScheduledThreadPool:创建一个定时线程池,支持定时及周期性任务执行
为何禁止使用 Executors
因为会造成不可预料的后果,尤其是 newFixedThreadPool 和 newCachedThreadPool 这两个方法。newFixedThreadPool传入的是一个无界的阻塞队列,理论上可以无限添加任务到线程池。当核心线程执行时间很长(比如 sleep10s),则新提交的任务还在不断地插入到阻塞队列中,最终造成OOM。newCachedThreadPool缓存线程池的最大线程数为 Integer 最大值。当核心线程耗时很久,线程池会尝试创建新的线程来执行提交的任务,当内存不足时就会报无法创建线程的错误。
在线程池内部主要包含以下几个部分:
worker 集合:保存所有的核心线程和非核心线程,其本质是一个HashSet
等待任务队列:当核心线程的个数达到 corePoolSize 时,新提交的任务会被先保存在等待队列中,其本质是一个阻塞队列 BlockingQueue
ctl:是一个 AtomicInteger 类型,二进制高 3 位用来标识线程池的状态,低 29 位用来记录池中线程的数量
处理流程:
1. 当前线程池中运行的线程数量还没有达到 corePoolSize 大小时,线程池会创建一个新线程执行提交的任务,无论之前创建的线程是否处于空闲状态
2. 当前线程池中运行的线程数量已经达到 corePoolSize 大小时,线程池会把任务加入到等待队列中,直到某一个线程空闲了,线程池会根据我们设置的等待队列规则,从队列中取出一个新的任务执行
3. 如果线程数大于 corePoolSize 数量但是还没有达到最大线程数 maximumPoolSize,并且等待队列已满,则线程池会创建新的线程来执行任务
4. 最后如果提交的任务,无法被核心线程直接执行,又无法加入等待队列,又无法创建“非核心线程”直接执行,线程池将根据拒绝处理器定义的策略处理这个任务
如果你没有为线程池设置 RejectedExecutionHandler。这时线程池会抛出 RejectedExecutionException 异常,拒绝策略:
java中的阻塞队列:
1.ArrayBlockingQueue: 由数组结构组成的有界阻塞队列
2.LinkedBlockingQueue: 由链表结构组成的有界阻塞队列
3. PriorityBlockingQueue : 支持优先级排序的无界阻塞队列
4. DelayQueue: 使用优先级队列实现的无界阻塞队列
5. synchronousQueue: 不存储元素的阻塞队列
6. LinkedTransferQueue: 由链表结构组成的无界阻塞队列
7. LinkedBlockingDeque: 由链表结构组成的双向阻塞队列