一 引言:
在前面的几篇博客中,我们知道,当需要使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPoolExecutor类开始讲起。
ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理,监控等等服务。
二 线程池的创建及参数说明
这里以一个最常用的构造方法来说明线程池的创建.
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
第一个参数5===》corePoolSize:核心线程池大小。可以理解为就是线程池的大小。有人会联想起数据库里面的连接池,认为这是初始连接大小,其实两者是有区别的。连接池的初始连接大小是在连接池启动时就会建立好多个(比方说这里的5个)数据库连接对象的。但是在线程池这里是不会初始建立5个线程对象的,而是要等具体的任务来了才会逐个建立线程。当任务执行完毕,线程池中的线程就会逐个中止并释放资源,但是仍然会保持5个空闲,继续等待接受新的任务。就是这个意思。
当然,在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
prestartCoreThread():初始化一个核心线程;
prestartAllCoreThreads():初始化所有核心线程
第二个参数10===》maximumPoolSize:池中允许的最大线程数。这里表示线程池中最多同时只能允许10个任务在执行。
第三个参数200===》keepAliveTime :当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。怎么理解呢?
打个比方:工厂里有正式员工5人(corePoolSize),突然来了10个任务,那么就要临时招5个人过来帮忙,任务做完了,老板并不想立刻辞退他们,而是花钱继续养着以防止又有大量的任务到来。但是如果一直没有多余的任务来,就不能一直养着,所以一段时间后就辞退他们。这里的一段时间就是这里的第三个参数200,也就是说线程空闲了200个单位时间就会被中止。那么这200个单位时间是秒,毫秒,还是分钟,小时呢,请看第四个参数。
第四个参数TimeUnit.MILLISECONDS===》unit:keepAliveTime 参数的时间单位。这里表示的是毫秒。
第五个参数new ArrayBlockingQueue<Runnable>(5)===》workQueue : 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。这个参数最不好理解。假如现在线程池里面有5个任务正在运行,这时候来了第6个任务,就会到此队列里面等待,来了第七个第八个同理排队等待,直到来了第11个任务,队列已满,但是线程池里面5个核心线程仍然没有停止,那么这时候就会创建新的线程来运行队列里面排队的任务,这时候正在运行的线程就是6个。当来了第15个任务之后,假如前面的任务还没有完成,那么这时候线程池里面一共有10个任务在跑了,已经达到了第二个参数maximumPoolSize:最大活跃线程数的限制,这时候线程池就不再接收新的任务了,就必须等待池子里面有空闲线程为止。
举个例子,有个公司面试,同时有5个面试官可以面试5个人。房间里面还有5个可以供预备面试的人排队等候的座位。这时候来了8个人,那么有5个人正在面试,3个人正在等待。然后面试者增加到12人,前面5个人还在面试,那么这时剩下的7人里面只有5个人可以坐在房间里面排队休息,还有2人只能站在门外。公司发现面试的人比较多,让人站在外面不近人情,然后又临时抽调2个面试官过来面试,这时候刚好7个在面试,5个在等待,没有站在门外的。好,然后又来人了,公司又临时抽调面试官过来。一直到房间里面有10个面试官正在面试,公司已经无法继续临时抽调面试官过来,那么再来的面试者只能被拒之门外了。就是这个道理,明白了没!
三 案例
/*
* 线程检测类
*/
class CheckTask implements Runnable {
ThreadPoolExecutor executor;
public CheckTask(ThreadPoolExecutor executor) {
this.executor=executor;
}
/**
* 线程检测
*/
public void showPoolInfo(ThreadPoolExecutor executor,int i){
System.out.println("=========第"+i+"次检测start===========");
System.out.println("线程池中核心线程数:"+executor.getCorePoolSize());
System.out.println("线程池中活跃线程数目:"+executor.getActiveCount());
System.out.println("线程池中允许的最大线程数目:"+executor.getMaximumPoolSize());
System.out.println("队列中等待执行的任务数目:"+executor.getQueue().size());
System.out.println("已经执行完成的任务数目:"+executor.getCompletedTaskCount());
System.out.println("=========第"+i+"次检测end===========");
}
public void run() {
int cnt=0;
while(true){
cnt++;
try {
Thread.sleep(1000);//每过1s检测一次
} catch (InterruptedException e) {
e.printStackTrace();
}
showPoolInfo(executor,cnt);
if(executor.isTerminated()){
break;
}
}
}
}
=========================================
/*
* 任务类
*/
class MyTask implements Runnable {
String theTask;
public MyTask(String theTask) {
this.theTask = theTask;
}
public void run() {
System.out.println("正在执行"+theTask);
try {
//需要一定时间秒执行完毕
Thread.currentThread().sleep((long)(Math.random()*10000));
} catch (InterruptedException e) {
System.out.println("线程:"+theTask+"被中断");
}
System.out.println("线程:"+theTask+"执行完毕");
}
}
=======================================
/* * 主线程类 */ public class ThreadPoolTest { public static void main(String[] args) { //创建线程池对象 ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
//该线程用于对线程池的检测 new Thread(new CheckTask(executor)).start();
//预先创建核心线程 executor.prestartAllCoreThreads(); for(int i=1;i<=15;i++){//执行15个任务,但最多只有10个线程同时运行。 MyTask myTask = new MyTask("task=="+i); executor.execute(myTask); } executor.shutdown(); } }
=================================运行结果================正在执行task==2 正在执行task==4 正在执行task==11 正在执行task==1 正在执行task==13 正在执行task==15 正在执行task==5 正在执行task==3 正在执行task==12 正在执行task==14 =========第1次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:10 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:5 已经执行完成的任务数目:0 =========第1次检测end=========== 线程:task==3执行完毕 正在执行task==6 线程:task==14执行完毕 正在执行task==7 =========第2次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:10 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:3 已经执行完成的任务数目:2 =========第2次检测end=========== 线程:task==7执行完毕 正在执行task==8 =========第3次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:10 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:2 已经执行完成的任务数目:3 =========第3次检测end=========== 线程:task==15执行完毕 正在执行task==9 线程:task==6执行完毕 正在执行task==10 =========第4次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:10 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:5 =========第4次检测end=========== 线程:task==8执行完毕 线程:task==12执行完毕 线程:task==9执行完毕 =========第5次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:7 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:8 =========第5次检测end=========== 线程:task==2执行完毕 线程:task==11执行完毕 =========第6次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:5 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:10 =========第6次检测end=========== 线程:task==1执行完毕 =========第7次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:4 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:11 =========第7次检测end=========== =========第8次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:4 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:11 =========第8次检测end=========== 线程:task==5执行完毕 =========第9次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:3 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:12 =========第9次检测end=========== 线程:task==4执行完毕 线程:task==13执行完毕 =========第10次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:1 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:14 =========第10次检测end=========== 线程:task==10执行完毕 =========第11次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:0 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:15 =========第11次检测end===========
============================
如果我们这时候把等候队列的5改成20,即new ArrayBlockingQueue<Runnable>(5) ====》new ArrayBlockingQueue<Runnable>(20) 即等候队列足够大
那么运行效果如下
正在执行task==1 正在执行task==2 正在执行task==3 正在执行task==4 正在执行task==5 =========第1次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:5 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:10 已经执行完成的任务数目:0 =========第1次检测end=========== 线程:task==4执行完毕 正在执行task==6 =========第2次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:5 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:9 已经执行完成的任务数目:1 =========第2次检测end=========== 线程:task==3执行完毕 正在执行task==7 =========第3次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:5 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:8 已经执行完成的任务数目:2 =========第3次检测end=========== 线程:task==1执行完毕 正在执行task==8 线程:task==8执行完毕 正在执行task==9 =========第4次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:5 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:6 已经执行完成的任务数目:4 =========第4次检测end=========== 线程:task==7执行完毕 正在执行task==10 线程:task==2执行完毕 正在执行task==11
......
==============================================第13次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:2 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:13 =========第13次检测end=========== =========第14次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:2 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:13 =========第14次检测end=========== 线程:task==12执行完毕 =========第15次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:1 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:14 =========第15次检测end=========== =========第16次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:1 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:14 =========第16次检测end=========== =========第17次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:1 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:14 =========第17次检测end=========== =========第18次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:1 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:14 =========第18次检测end=========== 线程:task==15执行完毕 =========第19次检测start=========== 线程池中核心线程数:5 线程池中活跃线程数目:0 线程池中允许的最大线程数目:10 队列中等待执行的任务数目:0 已经执行完成的任务数目:15 =========第19次检测end===========
这时候我们发现线程里面的活跃线程数始终不超过5个,因为队列的容量足够大,没有执行的任务都排队等候即可
关于线程入队列和任务丢弃原则请查看续篇。