构造参数
线程池ThreadPoolExecutor的部分构造参数说明:
- corePoolSIze: 核心线程数目
- runnableTaskQueue: 阻塞队列,保存等待执行的任务。有以下几种类型:
- ArrayBlockingQuque: 基于数组实现的有界阻塞队列, 队列大小由构造函数指定
- LinkedBlockingQueue: 基于链表实现的无界阻塞队列,吞吐量通常高于ArrayBlockingQuque(没验证)
- SynchronousQueue: 不存储元素的阻塞队列。 该队列挺有意思, 有时间研究一下
- PriorityBlockingQueue: 具有优先级的无界阻塞队列
- maxPoolISize: 线程池的最大线程数目
- RejectedExecutionHandler: 拒绝饱和策略,当队列和最大线程池都满的时候,若此时添加任务,由该策略来执行,默认情况是AbortPolicy, 表示直接抛出异常,也可以使用其他几种策略或者自定义策略
- keepAliveTime: 当工作线程空闲之后,还能活多久. 当任务很多且每个任务执行的时间较短,则可以适当调高该参数,减少线程的创建. 此处杀死的线程是指当前线程数大于核心线程数时,才有可能去杀死超过核心线程数且小于最大线程数的这些线程
执行流程
线程池中有三个基本概念:
- 核心线程数目
- 线程队列
- 最大线程数目
当往线程池中添加一个线程时,会执行如下判断流程:
- 首先判断线程池中线程数目是否小于规定的核心线程数,若是则会创建一个新的线程并执行该任务,若否则执行下一流程
- 判断线程池的队列是否已满,若队列未满,则将该任务加入队列中;若队列已满,则执行下一流程
- 判断线程池中线程数目是否小于规定的最大线程数,若是则会创建一个新的线程并执行该任务,若否则交由拒绝策略来进行处理.
具体流程如下图所示:
注意:
- 当提交一个线程到线程池中时,该线程首先会执行提交的任务,待执行完毕之后再循环的取队列中的任务去执行.
- 每次创建线程时都需要获取全局锁,包括创建核心线程和最大线程
下面这个代码如下:
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 0L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10)
);
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("活动线程:" + executor.getActiveCount() + " 线程数量:" + executor.getPoolSize());
}
}
}).start();
for (int i=0; i < 20; i++){
executor.submit(new TestRunnable(i));
}
}
static class TestRunnable implements Runnable{
public TestRunnable(int num) {
this.num = num;
}
private int num;
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
其中有一个监控线程,每隔一秒会打印出线程池的情况。刚开始时打印出结果是:
活动线程:5 线程数量:5
此时线程池创建除了最大的5个线程, 且都在执行任务。
当所有任务全部都执行完毕之后, 打印结果是:
活动线程:0 线程数量:2
此时任务全部都执行完毕, 所以线程池内部只保留了2个核心线程, 其他线程已经全部被杀死。
几种特定线程池
FixedThreadPool
线程数是固定的线程池
其构造函数如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());}
核心线程数和最大线程数相等,这样当往线程池添加任务时,若当前线程数小于核心线程数,则会新建线程,否则将任务放入无界队列中, 进行等待.该类型线程池少了一个当队列满之后,继续创建线程的步骤.
由以上可知, 该类型的线程池一旦线程数达到nThreads之后, 除非线程池被销毁, 否则线程池内将会一直有nThreads个线程存活。
该线程池的keepAliveTime设置为0我感觉没多大意义,因为核心线程数和最大线程数一样,所以线程池中不存在多余的空闲线程
SingleThreadExecutor
只有一个线程的线程池
线程池核心线程数和最大线程数都为1, 当往线程池中添加任务时,若线程池无运行线程,则会创建线程,否则加入无界队列中,等待线程从该队列中获取任务
ScheduledThreadPoolExecutor
具有定时任务功能的线程池
使用无界延迟队列DelayQueue.线程执行的任务都被封装成一个ScheduledFutureTask,添加定时任务时,会将这个ScheduledFutureTask放入到队列中。同时线程池中的线程会从队列中获取任务进行执行。
(1) 该线程池是怎样保证定时执行?
关键字: ScheduleFutureTask, PriorityQueue
主要是在ScheduleFutureTask类上,该类包含三个成员变量:
- 任务将要被执行的具体时间time
- 任务被添加到线程池时的序号number
- 任务执行的间隔周期period
队列DelayQueue内部封装了一个优先级队列PriorityQueue,该队列会对其中所有的ScheduleFutureTask进行排序,time越小排序越靠前。线程从队列中获取到期任务(即当前时间小于等于time),执行完成之后,再根据period重新设置time,然后将该任务重新放入队列中。
CachedThreadPool
根据需要创建新线程的线程池
其构造方法如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
由构造方法可知, 当有新任务进来时, 如果线程池有空闲线程(所以此处设置的线程存活时间较长,为60s),则使用该线程, 否则就会一直创建新线程来执行(因为使用了SynchronousQueue队列, 该队列不存储元素, 所以不会有任务被存储到队列中等待)。所以当任务处理速度比较缓慢时, 有可能会导致一直创建新线程, 最终导致CPU和内存资源耗尽。一般不适用该线程池。