最近的面试被频繁问到, 这里简单梳理下:
先说java.uitl.concurrent.ThreadPoolExecutor类, 这是线程池最核心的一个类, 提供了4个构造方法, 前三个到最后都是对第四个进行调用;
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
构造器里的几个核心参数的含义:
corePoolSize : 核心池的大小, 创建了线程池后默认不立即创建线程, 待到有任务来了再创建线程去执行, 直到线程数达到corePoolSize。 再来任务便加入阻塞队列, 队列满了之后再扩容, 直到最大线程数maximumPoolSize;再来任务的话就抛出任务拒绝的异常。
maximumPoolSize : 池子最大线程数
keepAliveTime : 线程过期时间, 无任务状态下线程最多保持多长时间终止
TimeUnit unit : 时间单位(分/秒/毫秒)
workQueue : 阻塞队列
简单测试具体的用法:
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0; i<15; i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
任务类MyTask:
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
执行的结果:
正在执行task 0
线程池中线程数目:1,队列中等待执行的任务数目:0,已执行的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行的任务数目:0
正在执行task 1
线程池中线程数目:4,队列中等待执行的任务数目:0,已执行的任务数目:0
正在执行task 2
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:1,已执行的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:2,已执行的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:3,已执行的任务数目:0
正在执行task 3
线程池中线程数目:5,队列中等待执行的任务数目:4,已执行的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行的任务数目:0
线程池中线程数目:6,队列中等待执行的任务数目:5,已执行的任务数目:0
正在执行task 4
线程池中线程数目:7,队列中等待执行的任务数目:5,已执行的任务数目:0
正在执行task 10
线程池中线程数目:8,队列中等待执行的任务数目:5,已执行的任务数目:0
线程池中线程数目:9,队列中等待执行的任务数目:5,已执行的任务数目:0
正在执行task 12
正在执行task 11
线程池中线程数目:10,队列中等待执行的任务数目:5,已执行的任务数目:0
正在执行task 13
正在执行task 14
task 0执行完毕
正在执行task 5
task 2执行完毕
task 1执行完毕
正在执行task 6
正在执行task 7
task 3执行完毕
正在执行task 8
task 4执行完毕
task 10执行完毕
正在执行task 9
task 14执行完毕
task 13执行完毕
task 12执行完毕
task 11执行完毕
task 5执行完毕
task 6执行完毕
task 7执行完毕
task 8执行完毕
task 9执行完毕
综上可以看出:
一开始线程池里是没有线程的,任务来了之后才开始创建线程,直到线程数达到核心池的大小(5个)。这时候再来任务的时候池子不会直接扩容,而是将任务加入阻塞队列,直到队列(5个)也满了再去扩容线程,直到扩到池子最大线程数(10个)终止。这时候池子线程数达到上限10个,队列阻塞也达到最大值5个,再来新的任务的话,就要报错了(抛出任务拒绝异常)。
在实际应用的时候,线程池的大小具体配成多少比较合适?
网上搜了下,比较一致的说法是:
线程数=CPU可用核心数/(1-阻塞系数),其中阻塞系数的取值在0和1之间。
简单来记就是:
IO密集型=2Ncpu(可以测试后自己控制大小,2Ncpu一般没问题)(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
计算密集型=Ncpu(常出现于线程中:复杂算法)
## Ncpu就是指具体的几核的CPU,像我这4核的配corePoolSize 参数的时候,配成4 - 8基本就是ok的!
文章参考:
https://www.cnblogs.com/dolphin0520/p/3932921.html
https://www.cnblogs.com/dennyzhangdd/p/6909771.html?utm_source=itdadao&utm_medium=referral