正文 :
我们来理解一下线程池:
线程池有几个关键参数:核心线程数、最大线程数、回收时间、队列。
这里细看一下线程池有这么几个重要的参数:
-
corePoolSize=>
线程池里的核心线程数量 -
maximumPoolSize
=> 线程池里允许有的最大线程数量 -
keepAliveTime=>
空闲线程存活时间 -
unit=>
keepAliveTime的时间单位,比如分钟,小时等 -
workQueue=> 缓冲
队列 -
threadFactory=>
线程工厂用来创建新的线程放入线程池 -
handler=>
线程池拒绝任务的处理策略,比如抛出异常等策略
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
//21
public class ThreadPoolTest {
// 任务数
private static int taskCount = 5;
// 实际完成任务数
private static AtomicInteger taskCountExecuted;
public static void main(String[] args) {
init();
}
private static void init(){
taskCountExecuted = new AtomicInteger(0);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
10, // 核心线程数
20, // 最大线程数
5, // 非核心线程回收超时时间
TimeUnit.SECONDS, // 超时时间单位
new ArrayBlockingQueue<>(30) // 任务队列
);
System.out.println("任务总数 = [" + taskCount + "]个");
long start = System.currentTimeMillis();
for(int i=0; i<taskCount; i++){
Runnable runnable = new Runnable() {
@Override
public void run() {
try{
Thread.sleep(500);
System.out.println("已执行第 [" + taskCountExecuted.addAndGet(1) + "] 个任务");
}catch (Exception ex){
ex.printStackTrace();
}
}
};
try{
// 默认拒绝策略会报错
threadPoolExecutor.execute(runnable);
}catch (Exception ex){
ex.printStackTrace();
taskCount = threadPoolExecutor.getActiveCount() + threadPoolExecutor.getQueue().size();
}
}
long end = 0;
while (threadPoolExecutor.getCompletedTaskCount() < taskCount){
end = System.currentTimeMillis();
}
System.out.println("[" + taskCountExecuted + "]个任务执行总耗时 = [" + (end - start) + "]ms");
threadPoolExecutor.shutdown();
}
}
分析一下:
当我们给一个任务数量的时候,线程池中的任务数小于核心线程数量10,这时所有的任务
会立刻执行。
如果我们再次添加任务超过核心线程数的话,多余的就要进入队列等待,但是如何任务数量大于
核心线程数+队列数量的时候就要看最大线程数和队列空间的之和差几就会开启几个非核心线程跑
任务,如果大于了最大线程数和队列空间的之和就会使用我们的策略了。
其他的解释 仅供交流学习:
场景一:提交5个任务,执行总耗时500ms
taskCount = 5;
1
执行结果:
任务总数 = [5]个
已执行第 [2] 个任务
已执行第 [3] 个任务
已执行第 [4] 个任务
已执行第 [1] 个任务
已执行第 [5] 个任务
[5]个任务执行总耗时 = [506]ms
分析:核心线程数为10,也就是说有10个线程数长期处于活动状态,即来任务立马就能执行,任务数5 < 核心线程数10,所以,5个任务立马执行完成,且是多线程并行执行,所以任务执行总耗时 = 500ms
场景二:提交10个任务,执行总耗时500ms
taskCount = 10;
1
执行结果:
任务总数 = [10]个
已执行第 [2] 个任务
已执行第 [8] 个任务
已执行第 [5] 个任务
…
已执行第 [3] 个任务
已执行第 [9] 个任务
已执行第 [10] 个任务
[10]个任务执行总耗时 = [507]ms
分析:任务数10 <= 核心线程数10,10个任务立马执行完成,所以任务执行总耗时 = 500ms。
场景三:提交11个任务,执行总耗时1000ms
taskCount = 11;
1
执行结果:
任务总数 = [11]个
已执行第 [1] 个任务
已执行第 [3] 个任务
已执行第 [4] 个任务
…
已执行第 [8] 个任务
已执行第 [7] 个任务
已执行第 [11] 个任务
[11]个任务执行总耗时 = [1009]ms
分析:任务执行总耗时 = 1000ms,别惊讶,这里是很多人没有搞懂线程池运行机制的关键点,虽然任务只多个一个,但是地11个任务不是立马执行的,核心线程数为10,第11个任务会进入到任务队列,等核心线程有空出来后会从任务队列中取出任务再来执行,因此任务总耗时 = 1000ms。
场景四:提交20个任务,执行总耗时1000ms
taskCount = 20;
1
执行结果:
任务总数 = [20]个
已执行第 [5] 个任务
已执行第 [4] 个任务
已执行第 [2] 个任务
…
已执行第 [16] 个任务
已执行第 [19] 个任务
已执行第 [20] 个任务
[20]个任务执行总耗时 = [1010]ms
分析:任务执行总耗时 = 1000ms,此处与前一个场景一样,第11到第20共10个任务会进入到任务队列,等核心线程有空出来后会从任务队列中取出任务再来执行,因为有10个核心线程,前10个任务执行完成后,任务队列中的10个任务正好由空出的10个核心线程来执行,因此任务总耗时 = 1000ms。
场景五:提交30个任务,执行总耗时1500ms
taskCount = 30;
1
执行结果:
任务总数 = [30]个
已执行第 [2] 个任务
已执行第 [6] 个任务
已执行第 [3] 个任务
…
已执行第 [23] 个任务
已执行第 [25] 个任务
已执行第 [30] 个任务
[30]个任务执行总耗时 = [1514]ms
分析:任务执行总耗时 = 1500ms,此处与前一个场景一样,第11到第30共20个任务会进入到任务队列,等核心线程有空出来后会从任务队列中取出任务再来执行,因为有10个核心线程,前10个任务执行完成后,从任务队列中取出10个任务由空出的10个核心线程来执行,执行完后在取出10个任务来执行,因此任务总耗时 = 1500ms。
场景六:提交40个任务,执行总耗时2000ms
taskCount = 40;
1
执行结果:
任务总数 = [40]个
已执行第 [1] 个任务
已执行第 [2] 个任务
已执行第 [4] 个任务
…
已执行第 [32] 个任务
已执行第 [34] 个任务
已执行第 [40] 个任务
[40]个任务执行总耗时 = [2016]ms
分析:任务执行总耗时 = 2000ms,此处与前一个场景一样,这里不再过多解释。
场景七:提交41个任务,执行总耗时2000ms
taskCount = 41;
1
执行结果:
任务总数 = [41]个
已执行第 [1] 个任务
已执行第 [2] 个任务
已执行第 [3] 个任务
…
已执行第 [40] 个任务
已执行第 [37] 个任务
已执行第 [41] 个任务
[41]个任务执行总耗时 = [2016]ms
分析:任务执行总耗时 = 2000ms,这里重点来了,我们知道第11到第40共30个任务进入到了任务队列中(任务队列大小为30),第41个任务就创建了一个非核心线程(最大线程数20 – 核心线程数10 = 10个非核心线程)来执行,此时线程池中的活跃线程数为11,第一批任务执行完后,会从任务队列中取出11个任务来执行,那就是 11 + 11 + 11 + 8 = 500ms * 4 = 2000ms。
场景八:提交45个任务,执行总耗时1500ms
taskCount = 45;
1
执行结果:
任务总数 = [45]个
已执行第 [3] 个任务
已执行第 [4] 个任务
…
已执行第 [43] 个任务
已执行第 [42] 个任务
已执行第 [45] 个任务
[45]个任务执行总耗时 = [1516]ms
分析:任务执行总耗时 = 1500ms,此处与前一个场景一样,我们知道第11到第40共30个任务进入到了任务队列中(任务队列大小为30),第41到第45共5个任务就创建了5个非核心线程(最大线程数20 – 核心线程数10 = 10个非核心线程)来执行,此时线程池中的活跃线程数为15,第一批任务执行完后,会从任务队列中取出15个任务来执行,那就是 15 + 15 + 15 = 500ms * 3 = 1500ms。
场景九:提交50个任务,执行总耗时1500ms
taskCount = 50;
1
执行结果:
任务总数 = [50]个
已执行第 [1] 个任务
已执行第 [9] 个任务
已执行第 [12] 个任务
…
已执行第 [45] 个任务
已执行第 [44] 个任务
已执行第 [50] 个任务
[50]个任务执行总耗时 = [1515]ms
分析:任务执行总耗时 = 1500ms,此处与前一个场景一样,我们知道第11到第40共30个任务进入到了任务队列中(任务队列大小为30),第41到第50共10个任务就创建了10个非核心线程(最大线程数20 – 核心线程数10 = 10个非核心线程)来执行,此时线程池中的活跃线程数为20,第一批任务执行完后,会从任务队列中取出20个任务来执行,那就是 20 + 20 + 10 = 500ms * 3 = 1500ms。
场景十:提交51个任务,执行总耗时1500ms
taskCount = 10;
1
执行结果:
任务总数 = [51]个
java.util.concurrent.RejectedExecutionException: Task com.example.springbootdemo.util.ThreadPoolTest$1@682a0b20 rejected from java.util.concurrent.ThreadPoolExecutor@3d075dc0[Running, pool size = 20, active threads = 20, queued tasks = 30, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at com.example.springbootdemo.util.ThreadPoolTest.init(ThreadPoolTest.java:48)
at com.example.springbootdemo.util.ThreadPoolTest.main(ThreadPoolTest.java:16)
已执行第 [3] 个任务
已执行第 [5] 个任务
…
已执行第 [49] 个任务
已执行第 [48] 个任务
已执行第 [50] 个任务
[50]个任务执行总耗时 = [1514]ms
分析:任务执行总耗时 = 1500ms,此处与前一个场景一样,由于线程池同时最大能接收50个任务(最大线程数20 + 任务队列大小30 = 50),所以第51个任务被拒绝了(线程池使用默认拒绝策略AbortPolicy),抛出了异常,DEMO中使用了try-catch捕获到了。
三、总结
任务数 <= 核心线程数,线程池中工作线程数 = 任务数;
核心线程数 < 任务数 <= (核心线程数 + 队列容量)时,线程池中工作线程数 = 核心线程数;
(核心线程数 + 队列容量) < 任务数 <= (最大线程数 + 队列容量)时,线程池中工作线程数 = (任务数 – 队列容量);