JDK的ThreadPoolExecutor类实现了线程池,在它的构造函数中,有五个参数,让我们看看这五个参数具体作用是什么。
- corePoolSize,核心线程池数量。
默认情况下会一直存活,当请求进来时,如果当时的存活线程数小于核心线程数量,就会去新创建线程来处理这个请求,即使有其他线程是空闲的。在allowCoreThreadTimeout参数设置会true时,核心线程也会超时退出,默认是false超时不退出。
- maxPoolSize,存活线程最大数量。
当线程数大于或等于核心线程,且任务队列已满时,线程池会继续创建新的线程处理请求,直到maxPoolSize的限制数量。如果当前存活的线程已经到了maxPoolSize的限制数量,且任务队列已满,线程池会拒绝处理任务并抛出异常。
- keepAliveTime,线程可空闲时间。
当线程空闲时间打到keepAliveTime时,线程会退出,直到存活线程数量等于核心线程数量。如果allowCoreThreadTimeout参数设置会true时,核心线程也会退出,直到线程数为0。
- TimeUnit,空闲线程的保留时间单位。
- workQueue ,缓存工作队列,用来缓存等待执行的任务。
- queueCapacity,任务队列容量。
- handler:表示当拒绝处理任务时的策略。
有四种取值:
(1)ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认此配置。
(2)ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
(3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。
(4)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。
任务队列的选用
任务队列一般选用LinkedBlockingQueue和Synchronous。注意当使用LinkedBlockingQueue时,一定要设置队列的大小,否则会默认设置队列的大小为Integer.MaxValue,可能会造成资源耗尽。
线程池的关闭
ThreadPoolExecutor提供了两个方法去关闭线程池。
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。
代码验证
我们来写一个测试类验证一下
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 5;
int keepAliveTime = 10;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
TimeUnit.SECONDS, workQueue);
try {
test(threadPoolExecutor, 3);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
//最后关闭线程池
threadPoolExecutor.shutdown();
}
}
public static void test(ThreadPoolExecutor threadPoolExecutor, int taskCount) {
for (int i = 0; i < taskCount; i++) {
threadPoolExecutor.submit(new TaskTest());
System.out.println("总任务数:" + threadPoolExecutor.getTaskCount() + ",已完成任务数:" + threadPoolExecutor.getCompletedTaskCount()
+ ",线程池线程数目:" + threadPoolExecutor.getPoolSize() + ",处于运行状态的数目:" + threadPoolExecutor.getActiveCount());
System.out.println("缓存队列任务数:"+threadPoolExecutor.getQueue().size());
}
}
static class TaskTest implements Runnable {
@Override
public void run() {
try {
//为了更好的效果,休眠一秒
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们先把核心线程数设置成2,最大线程数设置成5,缓存工作队列设置成2,然后把我们要执行的任务书设置成3个,看下运行的结果是什么。
总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕
pool-1-thread-2执行完毕
从最后执行完毕时打印的线程名称可知,最多只有两个线程在执行任务(核心线程数为2),而当前的总任务数达到核心任务数后,新的任务会被放到缓存队列中,因为缓存队列还没满,所以线程池不会去创建新的线程去处理任务。那我们再来试一下缓存队列满了之后会怎么样,我们把任务数设置成5,结果如下。
总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
总任务数:4,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:2
总任务数:5,已完成任务数:0,线程池线程数目:3,处于运行状态的数目:2
缓存队列任务数:2
pool-1-thread-1执行完毕
pool-1-thread-2执行完毕
pool-1-thread-3执行完毕
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕
果然,第三个线程出来了,当缓存队列也满了的时候,线程池会创建新的线程去处理任务,当然这个线程数也是会被我们设置的maximumPoolSize最大线程数限制的,最大允许线程数为5个,再加上缓存队列的两个,所以最大可处理为7个任务,当第八个任务来了的时候会怎么样呢,我们来试试,把任务数提高到8个,结果如下。
总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
总任务数:4,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:2
总任务数:5,已完成任务数:0,线程池线程数目:3,处于运行状态的数目:3
缓存队列任务数:2
总任务数:6,已完成任务数:0,线程池线程数目:4,处于运行状态的数目:4
缓存队列任务数:2
总任务数:7,已完成任务数:0,线程池线程数目:5,处于运行状态的数目:5
缓存队列任务数:2
Task java.util.concurrent.FutureTask@68de145 rejected from java.util.concurrent.ThreadPoolExecutor@27fa135a[Running, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0]
pool-1-thread-1执行完毕
pool-1-thread-3执行完毕
pool-1-thread-5执行完毕
pool-1-thread-2执行完毕
pool-1-thread-4执行完毕
pool-1-thread-1执行完毕
pool-1-thread-3执行完毕
最终被处理的任务数为七个,而且抛出了异常,第八个任务被舍弃掉了(默认ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常)。
我们再来测试一下两种关闭线程池方法的区别,先来试试shoudownNow()方法,在原来的代码上改动一下。
test(threadPoolExecutor, 4);
List<Runnable> runnableList = threadPoolExecutor.shutdownNow();
System.out.println("未处理任务数:"+runnableList.size());
结果如下:
总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
总任务数:4,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:2
未处理任务数:2
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕
关闭线程池后返回的未处理任务数为2,最终也只处理了前两条任务。我们再来试试shutdown()方法。
threadPoolExecutor.shutdown();
结果如下:
总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
总任务数:4,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:2
pool-1-thread-2执行完毕
pool-1-thread-2执行完毕
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕
四个任务全部执行完成。
今天的总结就到这里,共勉。