1.ThreadPoolExecutor
线程池
目录
可以通过ThreadPoolExecutor来创建一个线程池,ThreadPoolExecutor类一共有四个构造函数,
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
其中拥有最多参数的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
.......
}
- corePoolSize :核心线程数。 默认情况下线程池是空的, 只有任务提交时才会创建线程。 如果当前运行的线程数少于corePoolSize, 则创建新线程来处理任务; 如果等于或者多于corePoolSize, 则不再创建。 如果调用线程池的prestartAllcoreThread方法, 线程池会提前创建并启动所有的核心线程来等待任务。
- maximumPoolSize: 线程池允许创建的最大线程数。 如果任务队列满了并且线程数小于maximumPoolSize时, 则线程池仍旧会创建新的线程来处理任务。
- keepAliveTime: 非核心线程闲置的超时时间。 超过这个时间则回收。 如果任务很多, 并且每个任务的执行事件很短, 则可以调大keepAliveTime来提高线程的利用率。 另外, 如果设置allowCoreThreadTimeOut属性为true时, keepAliveTime也会应用到核心线程上。
- TimeUnit: keepAliveTime参数的时间单位。 可选的单位有天(DAYS) 、 小时(HOURS) 、 分钟(MINUTES) 、 秒(SECONDS) 、 毫秒(MILLISECONDS) 等。
- workQueue: 任务队列。 如果当前线程数大于corePoolSize, 则将任务添加到此任务队列中。 该任务队列是BlockingQueue类型的, 也就是阻塞队列。
- ThreadFactory: 线程工厂。 可以用线程工厂给每个创建出来的线程设置名字。 一般情况下无须设置该参数。
- RejectedExecutionHandler: 饱和策略。 这是当任务队列和线程池都满了时所采取的应对策略, 默认是AbordPolicy, 表示无法处理新任务, 并抛出RejectedExecutionException异常。 此外还有3种策略, 它们分别如下。
(1) CallerRunsPolicy: 用调用者所在的线程来处理任务。 此策略提供简单任务的提交速度。
(2) DiscardPolicy: 不能执行的任务, 并将该任务删除。
(3) DiscardOldestPolicy: 丢弃队列最近的任务, 并执行当前的任务
2.线程池的处理流程和原理
当向线程池提交任务时, 线程池是这样处理的。
(1) 提交任务后, 线程池先判断线程数是否达到了核心线程数(corePoolSize) 。 如果未达到核心线程数, 则创建核心线程处理任务; 否则, 就执行下一步操作。
(2) 接着线程池判断任务队列是否满了。 如果没满, 则将任务添加到任务队列中; 否则, 就执行下一步操作。
(3) 接着因为任务队列满了, 线程池就判断线程数是否达到了最大线程数。 如果未达到, 则创建非核心线程处理任务; 否则, 就执行饱和策略, 默认会抛出 RejectedExecutionException异常。
3.更好地了解线程池的原理
(1) 如果线程池中的线程数未达到核心线程数, 则创建核心线程处理任务。
(2) 如果线程数大于或者等于核心线程数, 则将任务加入任务队列, 线程池中的空闲线程会不断地从任务队列中取出任务进行处理。
(3) 如果任务队列满了, 并且线程数没有达到最大线程数, 则创建非核心线程去处理任务。
(4) 如果线程数超过了最大线程数, 则执行饱和策略。
4.线程池的种类
如果对阻塞队列不了解的可以阅读这篇文章java线程和进程(阻塞队列)_小安雨的博客-CSDN博客_java 进程阻塞阻塞队列简介,Java中的阻塞队列,阻塞队列的实现原理,阻塞队列的使用场景https://blog.csdn.net/fry3309/article/details/122058587
通过直接或者间接地配置ThreadPoolExecutor的参数可以创建不同类型的ThreadPoolExecutor, 其中有 4种线程池比较常用, 它们分别是 FixedThreadPool、 CachedThreadPool、 SingleThreadExecutor和ScheduledThreadPool。 下面分别介绍这4种线程池。
4.1 FixedThreadPool
FixedThreadPool(Fixed翻译中文:固定) 是可重用固定线程数的线程池。 在 Executors 类中提供了创建FixedThreadPool的方法,如下代码。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool的corePoolSize和maximumPoolSize都是设置为创建FixedThreadPool指定的参数的nThreads,也就是说FixedThreadPool只有核心线程,是没有非核心线程的,KeepAliveTime为0L,也就是说多余的线程会被立即终止。因为不会产生多余的线程。所以KeepAliveTime是无效的参数。另外任务队列采用是无界的阻塞队列LinkedBlockingQueue。FexidThreadPool的execute方法执行示意图。
当执行execute方法时,如果当前运行的线程未达到corePoolSize(核心线程数)时,就创建新的核心线程来处理任务,如果当达到核心线程数时则把当前任务添加到阻塞队列里(LinkedBlocingQueue),
总结:FixedThreadPool就是一个有固定数量核心线程的线程池, 并且这些核心线程不会被回收。 当线程数超过corePoolSize 时, 就将任务存储在任务队列中; 当线程池有空闲线程时, 则从任务队列中去取任务执行。
4.2CachedThreadPool
CachedThreadPool是一个根据需要创建线程的线程池
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
CachedThreadPool的corePoolSize为0, maximumPoolSize设置为Integer.MAX_VALUE, 这意味着CachedThreadPool没有核心线程, 非核心线程是无界的。 keepAliveTime设置为60L, 则空闲线程等待新任务的最长时间为 60s。 在此用了阻塞队列 SynchronousQueue, 它是一个不存储元素的阻塞队列, 每个插入操作必须等待另一个线程的移除操作, 同样任何一个移除操作都等待另一个线程的插入操作。 CachedThreadPool的execute方法的执行示意图
当执行execute方法时, 首先会执行SynchronousQueue的offer方法来提交任务, 并且查询线程池中是否有空闲的线程执行SynchronousQueue的poll方法来移除任务。 如果有则配对成功, 将任务交给这个空闲的线程处理; 如果没有则配对失败, 创建新的线程去处理任务。 当线程池中的线程空闲时, 它会执行SynchronousQueue的poll方法, 等待SynchronousQueue中新提交的任务。 如果超过 60s 没有新任务提交到SynchronousQueue, 则这个空闲线程将终止。 因为maximumPoolSize 是无界的, 所以如果提交的任务大于线程池中线程处理任务的速度就会不断地创建新线程。 另外, 每次提交任务都会立即有线程去处理。
总结:CachedThreadPool 比较适于大量的需要立即处理并且耗时较少的任务。
4.3SingleThreadExecutor
SingleThreadExecutor是使用单个工作线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
corePoolSize和maximumPoolSize都为1, 意味着SingleThreadExecutor只有一个核心线程, 其他的参数都和FixedThreadPool一样。
当执行execute方法时, 如果当前运行的线程数未达到核心线程数, 也就是当前没有运行的线程, 则创建一个新线程来处理任务。 如果当前有运行的线程, 则将任务添加到阻塞队列LinkedBlockingQueue中。
总结: SingleThreadExecutor能确保所有的任务在一个线程中按照顺序逐一执行。
4.4ScheduledThreadPool
ScheduledThreadPool是一个能实现定时和周期性任务的线程池。
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
这里创建了ScheduledThreadPoolExecutor, ScheduledThreadPoolExecutor继承自ThreadPoolExecutor, 它主要用于给定延时之后的运行任务或者定期处理任务。 ScheduledThreadPoolExecutor 的构造方法如下所示:
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
从上面的代码可以看出, ScheduledThreadPoolExecutor 的构造方法最终调用的是ThreadPoolExecutor的构造方法。 corePoolSize是传进来的固定数值, maximumPoolSize的值是Integer.MAX_VALUE。 因为采用的DelayedWorkQueue是无界的, 所以maximumPoolSize这个参数是无效的。 ScheduledThreadPoolExecutor的execute方法的执行示意图
当执行 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate 或者scheduleWithFixedDelay方法时, 会向DelayedWorkQueue 添加一个 实现RunnableScheduledFuture 接口的ScheduledFutureTask(任务的包装类) ,并会检查运行的线程是否达到 corePoolSize。 如果没有则新建线程并启动它, 但并不是立即去执行任务, 而是去 DelayedWorkQueue 中取ScheduledFutureTask, 然后去执行任务。 如果运行的线程达到了corePoolSize时, 则将任务添加到DelayedWorkQueue中。 DelayedWorkQueue会将任务进行排序, 先要执行的任务放在队列的前面。
总结: 其跟此前介绍的线程池不同的是, 当执行完任务后, 会将ScheduledFutureTask中的time变量改为下次要执行的时间并放回到DelayedWorkQueue中