java线程池之ThreadPoolExecutor(一)

来源:互联网 发布:淘宝怎么才能延时收货 编辑:程序博客网 时间:2024/06/13 18:08

一 引言:

        在前面的几篇博客中,我们知道,当需要使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在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个,因为队列的容量足够大,没有执行的任务都排队等候即可
关于线程入队列和任务丢弃原则请查看续篇。