三.多线程JUC篇-3.15 ThreadPoolExecutor
1.构造参数详细讲解
- corePoolSize:常驻核心线程数,也就是说这是线程池中始终存活的线程,即使是空闲状态,也不会被销毁,除非关闭线程池。但有个特殊,设置了allowCoreThreadTimeout
- maximumPoolSize:最大线程数,线程池中最多能存活的线程数
- keepAliveTime:空闲线程存活时间,配合下一个参数unit,当线程池中的线程数量超过corePoolSize,会清空多余空闲线程,以便使线程池数量保持到corePoolSize数量
- unit: 空闲线程存活时间的单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS等
- workQueue: 工作队列(阻塞队列、等待队列),调用submit方法,任务首先进入此队列中,而不是立即执行,队列的容量决定了能有多少请求被阻塞队列接受
- threadFactory: 线程工厂,一般使用默认Executors.defaultThreadFactory()即可
- handler:线程池的拒绝策略。当等待队列已经满了,再也塞不下新的任务;同时,线程池也满了(即达到maximumPoolSize及workQueue满),无法为新任务服务。这时,我们需要拒绝策略机制合理的处理这个问题
2. 任务什么时候被执行
有时候,您会发现提交的任务,并没有被立即执行,只执行了corePoolSize个任务。这个可能会给我们带来困惑,还没有达到maximumPoolSize,怎么还有部分任务不执行呢?其实是新任务进入了workQueue,workQueue没有满。有二种情况下,剩余部分任务会被执行,第一种workQueue满了,第二种线程池中有线程执行完任务后。
- 初次分配时,线程池会分配corePoolSize数量的线程,用于执行当前提交的任务
- 任务提交超过corePoolSize数量的部分,会暂时放至workQueue;待线程池中有空闲线程时,会从workQueue取任务
- 提交的任务过多,以至于workQueue也放下去了,线程池会在分配线程直到maximumPoolSize;如果线程池中的某个线程执行完了,又没有达到keepAliveTime时,会从workQueue取任务
提交的任务更多了,以至于超过了maximumPoolSize和workQueue大小,会执行handler策略
public class ThreadPoolExecutorBuild {
public static void main(String[] args) {
ThreadPoolExecutor executorService = (ThreadPoolExecutor)buildThreadPoolExecutor();
int activeCount = -1;
int queueSize = -1;
while(true){
if (activeCount != executorService.getActiveCount() || queueSize != executorService.getQueue().size()) {
System.out.println(executorService.getActiveCount());
System.out.println(executorService.getCorePoolSize());
System.out.println(executorService.getMaximumPoolSize());
System.out.println(executorService.getQueue().size());
activeCount = executorService.getActiveCount();
queueSize = executorService.getQueue().size();
System.out.println("==================");
}
}
}
private static ExecutorService buildThreadPoolExecutor(){
ExecutorService executorService = new ThreadPoolExecutor(1, 2,
30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
r->{ Thread t = new Thread(r);return t;},
new ThreadPoolExecutor.AbortPolicy());
//ToDo
return executorService;
}
private static void sleepSeconds(long seconds){
System.out.println("* " + Thread.currentThread().getName() + " *");
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(1)executorService.execute(()->sleepSeconds(10));
//执行一个任务,线程池为当前任务分配了一个线程,可以发现活跃线程数1,10秒后任务执行完后,
//活跃线程数0
* Thread-0 *
=====14:55:04=====
1
1
2
0
=====14:55:14=====
0
1
2
0
(2)executorService.execute(()->sleepSeconds(5));
executorService.execute(()->sleepSeconds(10));
//执行二个任务,可以发现依然活跃线程数1,但工作队列中有一个任务,5秒后第一个任务执行完,
//第二个任务从工作队列中取出并执行,使用了原线程(还是Thread-0,因为corePoolSize保证始
//终有一个)执行任务
* Thread-0 *
=====14:56:28=====
1
1
2
1
* Thread-0 *
=====14:56:33=====
1
1
2
0
=====14:56:43=====
0
1
2
0
(3)executorService.execute(()->sleepSeconds(10));
executorService.execute(()->sleepSeconds(5));
executorService.execute(()->sleepSeconds(8));
//执行三个任务,我们会发现此时线程池分配了二个线程为三个任务(因为工作队列满了,所以
//分配的线程数超过了corePoolSize),待第三个任务执行完,第二个任务开始执行了
* Thread-0 *
* Thread-1 *
=====15:00:22=====
2
1
2
1
* Thread-1 *
=====15:00:30=====
2
1
2
0
=====15:00:32=====
1
1
2
0
=====15:00:35=====
0
1
2
0
3.线程池的关闭
- submit()、execute():非阻塞方法,会立即返回,因为任务首次会放置在workQueue,然后线程池在从workQueue取任务执行
- shutdown():非阻塞方法,会立即返回
- shutdownNow():非阻塞方法,会立即返回
- awaitTermination():阻塞方法,接收timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。
3.1 shutdown和shutdownNow区别
(1) shutdown
- 开始一个有序的关闭,在关闭中,之前提交的任务会被执行(包含正在执行的,在阻塞队列中的)
- 新任务会被拒绝
- 中断空闲线程
- 当前方法不会等待之前提交的任务执行结束,可以使用awaitTermination()
(2) shutdownNow
- 返回正在等待被执行的任务列表
- 新任务会被拒绝
- 中断所有线程(shutdown只是中断空闲线程)
(3) isShutdown
只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true
(4) isTerminated
当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true
(5) 中断
如果线程中没有sleep 、wait、Condition、定时锁等可中断点,interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出
4.监控线程池
(1) 如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。
(2)可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性:
- taskCount:线程池需要执行的任务数量,是个近似值。
- completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount,是个近似值。
- largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
- poolSize:线程池当前的线程数总量,包括活动的线程与闲置的线程。
- activeCount:获取活动的线程数。
还没有评论,来说两句吧...