JAVA线程池全解

我们都知道线程池有很多好处:
通过重复利用已经创建好的线程,可以减少创建线程时的资源消耗。
如果不限制线程的数量,不仅会大量消耗系统内存,还会照成系统的不稳定。
使用线程池,可以控制线程的数量,还可以对所有线程进行统一的管理,好处不言而喻。

一、BlockingQueue

先看一下阻塞队列的代码关系:interface BlockingQueue extends Queue
我们发现这是一个继承了Queue的接口。我们发现这是一个继承了Queue的接口。首先Queue接口有如下方法:

TablesThrows ExceptionSpecial Value
插入类add(o)offer(o)
删除类remove(o)poll(o)
获取类element(o)peek(o)
意义如果这个操作不能立即执行,那么抛出异常如果这个操作不能立即执行,那么相应返回(true/false)

我们再看看BlockingQueue接口的方法
BlockingQueue有四种类型的插入,删除,和获取元素方法,归类如下:

TablesThrows ExceptionSpecial ValueBlocksTimes Out
插入类add(o)offer(o)put(o)offer(o, timeout, timeunit)
删除类remove(o)poll(o)take(o)poll(timeout, timeunit)
插入类add(o)offer(o)put(o)offer(o, timeout, timeunit)
获取类element(o)peek(o)
返回意义若不能立即执行,那么抛出异常若不能立即执行,那么相应返回(true/false)若不能立即执行,那么操作阻塞等待直到可以执行若不能立即执行,那么操作阻塞等待,但是有限定时间,如果超过时间还没有完成,返回错误信息

我们可以很明显的看到BlockingQueue加入了阻塞等待的操作,可以理解成如果队列满了,插入任务就在门口等着,不抛出错误信息,直到有元素从队列中取出,队列有空位了,再进行插入操作,相应的你还可以加入等待超时机制,如果过时了,就不等了。

public class BlockingQueueTest {
    public static void main(String[] args) {
        //初始化队列长度只有3的队列
        final BlockingQueue<String> blockingque = new ArrayBlockingQueue<String>(3);
        Thread Putter = new Thread(new Runnable(){
            @Override
            public void run() {
                for(int i=1;i<10;i++){
                    try {
                        blockingque.put("货物"+i);
                        System.out.println("成功往队列中放入货物"+i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Putter.start();
        Thread taker = new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    try {
                        //取得时间延长,模拟取得时间远大于放入时间
                        Thread.sleep(3000);
                        String cargo = blockingque.take();
                        System.out.println("取出货物: "+cargo);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        taker.start();  
    }
}

输出结果为:

成功往队列中放入货物1
成功往队列中放入货物2
成功往队列中放入货物3
取出货物: 货物1
成功往队列中放入货物4
取出货物: 货物2
成功往队列中放入货物5
取出货物: 货物3
成功往队列中放入货物6
取出货物: 货物4
成功往队列中放入货物7
取出货物: 货物5
成功往队列中放入货物8
取出货物: 货物6
成功往队列中放入货物9
取出货物: 货物7
取出货物: 货物8
取出货物: 货物9

我们可以看到队列长度为3,队列放满3个后,put()方法就处于blocking的状态等待队列有位子
过了3秒后,getter开始从队列中取货物,一有空位,put()方法就得以继续执行。

二、实现一个简单的线程池

首先我们关注一个 “生产者消费者” 的情景。
生产者:不断产生新的任务,比如查询数据库,执行某些业务逻辑等。
消费者:完成任务。

那么把这两者连接起来的就要用到我们的BlockingQueue了。
生产者将不断产生的任务放入到队列中,如果队列满了,生产者等待。
消费者不断的从队列中取出任务解决,当队列空了,消费者等待新任务到来。
首先BlockQueue的长度我们要限制,不然如果解决者的解决能力跟不上生产者的,这个任务队列就会越来越多。
接着我们还需要限定消费者的个数,就是我们所谓的线程池中能同时运行的最多的线程数,如果线程数太多的话会严重影响系统的稳定性。
那么我们根据这两个参数写一个简单的线程池

线程池:

public class ThreadPool {
    //用blockingQueue创建一个任务队列,初始化长度为5
    private BlockingQueue<Runnable> tasksQueue = new ArrayBlockingQueue<Runnable>(5);
    //定义线程池中消费者最大数量
    private int consumers = 3;
    //这个方法提供给所有的任务生产者,产生新的任务插入
    public void insertTask(Runnable task) throws InterruptedException{
        tasksQueue.put(task);
    }
    //线程池的初始化
    ThreadPool(){
        //激活消费者,等待问题到来
        for(int i=1;i<=consumers;i++){
            Solver consumer = new Solver(tasksQueue,i);
            consumer.start();
        }
    }
}

接下来定义 消费者 逻辑:

public class Solver extends Thread{
    //引用线程池的任务队列,消费者不断的从里面取得任务去解决
    private BlockingQueue<Runnable> taskQueue = null;
    String name;
    Solver(BlockingQueue<Runnable> tasks,int name){
        this.taskQueue = tasks;
        this.name = String.valueOf(name);
    }
    public void run(){
        try {
            while(true){
                //从队列中取出任务执行,注意这里用了take方法,所以如果队列空了,那么线程会等待,直到有任务来了,继续执行
                Runnable task = taskQueue.take();
                System.out.println("消费者"+name+"接收了一个任务");
                task.run();
                System.out.println("消费者"+name+"解决了一个任务");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

我们在上面的例子可以看到。
这个线程池中最大的线程数是3,就是最多只能同时有3个消费者线程执行,消费者会监视线程池的任务队列,只要队列中有任务,就会取出来执行。

接下来我们定义 生产者 的逻辑:

public class ProblemCreater {
    public static void main(String[] args) throws Exception {
        //初始化线程池
        ThreadPool threadPool = new ThreadPool();
        //生成者不断产生任务
        for(int i=1;i<10;i++){
            //定义一个新的任务
            Runnable task = new Runnable(){
                public void run(){
                    Random random = new Random();
                    //随机一个数字模拟需要解决的时间
                    int randomTime = Math.abs(random.nextInt())%20;
                    //System.out.println("这个任务需要解决时间为:"+randomTime);
                    try {
                        Thread.sleep(randomTime*1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            //将问题插入到线程池任务队列中
            threadPool.insertTask(task);
            System.out.println("插入新的任务"+i);
        }
    }
}

到此我们已经实现好了一个非常简单的线程池,将线程的创建与执行过程分离开,而不是将线程的生命周期管理和任务的执行过程绑定在一起,如果只是想简单的丢一个任务进去执行,我们只需要将任务的执行过程封装到一个Runnable接口中就可以了。而对于那些需要返回结果的任务,我们可以将其封装到Callable接口里面。

三、ExecutorService接口以及ThreadPoolExecutor

我们来了解一下java为我们提供的线程池实现—— ExecutorService接口
它位于jdk的java.util.concurrent包下。
JDK提供了这么两个类来实现这个接口:

  1. ThreadPoolExecutor
  2. ScheduledThreadPoolExecutor

我们这篇文章只介绍一下ThreadPoolExecutor类(ScheduledThreadPoolExecutor类类似,多加入了计划任务功能。
我们首先看看怎么用ThreadPoolExecutor类初始化一个线程池:

//初始化一个线程池  
//核心线程数  
int  corePoolSize  = 5;  
//最大线程数  
int  maxPoolSize   = 10;  
//空闲线程最大存活时间  
long keepAliveTime = 5000;  
//任务队列  
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(5);  
ExecutorService threadPoolExecutor =  
        new ThreadPoolExecutor(  
                corePoolSize,  
                maxPoolSize,  
                keepAliveTime,  
                TimeUnit.MILLISECONDS,  
                queue  
        );  

这个类完全实现了一个类似于我们上一篇文章中实现的线程池,它包含以下几个属性:
1. corePoolSize
核心线程数:即使没有任何任务过来,线程池里面也会有保持的最基本线程数。
2. maximumPoolSize
最大线程数(即使任务特别多,线程池里的线程数也不会超过它)
3. keepAliveTime
空闲线程最大存活时间
4. blockingQueue
任务队列,用来存放待处理的任务。可以选择以下几个阻塞队列。

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
PriorityBlockingQueue:一个具有优先级得无限阻塞队列。
那么整个类的结构就如下图所示:

要使用这个线程池的话,可以使用它提供给我们的如下方法:

  1. execute(Runnable)
  2. submit(Runnable)
  3. submit(Callable)
  4. invokeAny(…)
  5. invokeAll(…)

execute()和submit()可以向这个线程池提交单个任务。他们的区别是:
使用execute提交任务,但是execute方法没有返回值,所以无法判断任务知否被线程池执行成功。
使用submit 方法来提交任务,它会返回一个future对象,那么我们可以通过这个future对象来判断任务是否执行成功
invokeAll可以直接把一个List类型的任务列表一次性的提交给线程池执行。


那么接下来我们就用 ThreadPoolExecutor 来创建一个线程池,改写一下我们上一篇文章的例子:

public class ProblemCreater {  
    public static void main(String[] args) throws Exception {  
        //初始化线程池  
        //核心线程数  
        int  corePoolSize  = 5;  
        //最大线程数  
        int  maxPoolSize   = 10;  
        long keepAliveTime = 5000;  

        ExecutorService threadPoolExecutor =  
                new ThreadPoolExecutor(  
                        corePoolSize,  
                        maxPoolSize,  
                        keepAliveTime,  
                        TimeUnit.MILLISECONDS,  
                        new ArrayBlockingQueue<Runnable>(5)  
                );  

        //生成者不断产生任务  
        for(int i=1;i<10;i++){  
            //定义一个新的任务  
            Runnable task = new Runnable(){  
                public void run(){  
                    Random random = new Random();  
                    //随机一个数字模拟需要解决的时间  
                    int randomTime = Math.abs(random.nextInt())%20;  
                    try {  
                        Thread.sleep(randomTime*1000);  
                        System.out.println("任务完成,花费时间为:"+randomTime);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                }  
            };  
            //将问题插入到线程池任务队列中  
            threadPoolExecutor.execute(task);  
            System.out.println("插入新的任务"+i);  
        }  
    }  
}  

我们再回头看看这个 ThreadPoolExecutor 的初始化,我们需要给它传递5个参数。核心线程数,最大线程数,线程存活时间,时间单位,还有阻塞队列的类型。
这个过程还是比较繁琐的。其实Java帮我们简化了这个过程,我们可以根据不同的情景,直接用一行代码创建一个合适的线程池。
实现这个功能的就是 java.util.concurrent.Executors类。

四、Executors类

上一章我们介绍了ExecutorService接口,以及它的实现类ThreadPoolExecutor。
那么这里我们将介绍Executors类,它可以更进一步的简化我们的工作,直接创建一些预定义过的线程池

这个类也在java.util.concurrent包下。它有如下的几个比较常用的创建线程池的方法:

一:newFixedThreadPool

创建一个线程可重用的并且线程数固定的线程池。

二:newCachedThreadPool

创建一个可根据实际情况动态维持线程数的线程池,当任务到来时,如果有已经构造好的空闲线程将重用它们,不创建新的线程。
如果没有可用的空闲线程,则创建一个新线程并添加到池中。并且会终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。

三:newSingleThreadExecutor

创建一个使用单个线程的 ExecutorService,以无界队列方式来运行该线程。
关于这3类的线程池我引用一下《thinking in java》对它们的描述:

使用FixedThreadPool,你可以一次性的预先执行代价高昂的线程分配,因而也就可以限制线程数的数量,这可以节省时间,因为你不用为每一个任务都固定的付出创建线程的开销。在事件驱动的系统中,需要线程的事件处理器,通过直接从池中获取线程,也可以如你所愿地尽快得到服务。你不会滥用可获得的资源,因为FixedThreadPool使用的Thread对象的数量是有界的。

对于CachedThreadPool,它在程序的执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的ExecutorService的首选。只有当这种方式会引发问题时,你才需要切换到FixedThreadPool。

SingleThreadExecutor就是线程数量为1的FixedThreadPool。这对于你希望在另一个线程中连续运行的任何事物(长期存活的任务)来说,都是非常有用的,例如监听进入套接字连接的任务


我们再来看一下它们的源码,比如FixedThreadPool:

public static ExecutorService newFixedThreadPool(int nThreads) {  
        return new ThreadPoolExecutor(nThreads, nThreads,  
                                      0L, TimeUnit.MILLISECONDS,  
                                      new LinkedBlockingQueue<Runnable>());  
    }  

所以在本质上,就是创建了一个我们上一篇文章介绍过的ThreadPoolExecutor类。


接下来我们写一个完整的,使用它们的例子:
下面给出了一个网络服务的简单结构,这里线程池中的线程作为传入的请求。它使用了预先配置的 Executors.newFixedThreadPool(int) 方法创建线程池:

class NetworkService implements Runnable {  
    private final ServerSocket serverSocket;  
    private final ExecutorService pool;  

    public NetworkService(int port, int poolSize)  
        throws IOException {  
      serverSocket = new ServerSocket(port);  
      pool = Executors.newFixedThreadPool(poolSize);  
    }  

    public void run() { // run the service  
      try {  
        for (;;) {  
          pool.execute(new Handler(serverSocket.accept()));  
        }  
      } catch (IOException ex) {  
        pool.shutdown();  
      }  
    }  
}  

class Handler implements Runnable {  
    private final Socket socket;  
    Handler(Socket socket) { this.socket = socket; }  
    public void run() {  
      // read and service request on socket  
    }  
}  

五、ThreadPoolExecutor源码分析

通过前面的章节,我们学习了可以通过ThreadPoolExecutor来创建一个线程池。
那么接下来我们分析一下ThreadPoolExecutor的源码,看看它具体是如何工作的。
我们看一下使用execute(Runnable task)执行一个任务的时候,到底发生了什么(代码进过简化):
先简单描述一下当我们提交一个任务到线程池中后发生了什么:
这里写图片描述

    public void execute(Runnable task) {
        //取出当前线程池活跃的线程数。
        //ctl是一个原子类型的对象(final AtomicInteger ctl),用来保存当前线程池的线程数以及线程池的状态。
        int c = ctl.get();
        //如果当前的活跃线程数小于核心线程数,即使现在有空闲线程,也创建一个新的线程,去执行这个任务
        if (workerCountOf(c) < corePoolSize) {
            //创建一个新的线程,去执行这个任务。
            if (addWorker(task, true))
                return;
            //如果执行到这一句说明任务没有分配成功。
            //所以获得当前线程池状态值,为后面的检查做准备。
            c = ctl.get();
        }
        //如果大于核心线程数,检查一下线程池是否还处于运行状态,并尝试把任务放入到blockingQueue任务队列中。
        if (isRunning(c) && workQueue.offer(task)) {
            //这里再次检查一下线程池的状态
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(task))
                //如果线程池不处于运行状态的话,就把我们刚才添加进任务队列中的任务移出,并拒绝这个任务。
                reject(task);
            //检查如果当前线程池中的线程数,如果为0了,就为线程池创建新线程(因为有可能之前存活的线程在上一次检查过后死亡了)
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //执行到这一句,说明队列满了。这时,如果当前线程池中的线程数还没有超过最大线程数,就创建一个新的线程去执行这个任务,如果失败就拒绝这个任务。
        else if (!addWorker(task, false))
            reject(task);
    }

六、可以返回结果的“Runnable”

我们知道ExecutorService框架使用Runnble作为其基本的任务表示形式。但是它有很大的局限性就是它不能返回一个值,或者抛出一个受检查的异常
实际上许多需要知道执行结果的任务都需要一定的执行时间的,比如执行数据库的查询,或者从网络上获取一些资源,更或者进行一些比较复杂的计算。对于这些任务Callable是一种更好的抽象。你可以把它当成有返回值的“Runnable”,它的call()方法就相当于Runnable的run()方法。但关键是它的call()方法将返回一个值,并可能抛出一个异常。
那么怎么在ExecutorService框架中很好的使用Callable呢。这里就需要使用到Feture接口。
ExecutorService中的所有submit方法都将返回一个Future对象,从而可以将Callable提交给ExecutorService,并得到一个Future用来获得任务的执行结果或者取消任务。

public class Test {  
    private final ExecutorService executor = Executors.newFixedThreadPool(3);  
    public void runTheTask(){  
        Future<String> future = executor.submit(new Callable<String>(){  
            @Override  
            public String call() throws Exception {  
                Thread.sleep(3000);  
                return "result";  
            }
        });  
        try {  
            System.out.println( future.get());  
        } catch (InterruptedException | ExecutionException e) {  
        }  
    } 
    public static void main(String args[]){  
        Test t = new Test();  
        t.runTheTask();  
    }  
}  

运行后在future.get()步会阻塞 直到3秒后 返回结果“result”
上面的线程只执行了一个Callable任务。
但有某些情景下需要我们执行好几个Callable任务,并且要获得它们的返回结果,代码就变得很不好控制了

public class Test {  
    private final ExecutorService executor = Executors.newFixedThreadPool(3);  
    public void runTheTask(){  
        Future<String> future1 = executor.submit(new Callable<String>(){  

            @Override  
            public String call() throws Exception {  
                Thread.sleep(3000);  
                return "result1";  
            }  

        });  
        Future<String> future2 = executor.submit(new Callable<String>(){  

            @Override  
            public String call() throws Exception {  
                Thread.sleep(3000);  
                return "result2";  
            }  

        });  
        Future<String> future3 = executor.submit(new Callable<String>(){  

            @Override  
            public String call() throws Exception {  
                Thread.sleep(3000);  
                return "result3";  
            }  

        });  
        try {  
            System.out.println( future1.get());  
            System.out.println( future2.get());  
            System.out.println( future3.get());  
        } catch (InterruptedException | ExecutionException e) {  
        }  
    }  

    public static void main(String args[]){  
        Test t = new Test();  
        t.runTheTask();  
    }  
}  

可以看到 上面的代码非常难看而且不好控制。
幸运的是,在这种情况下我们可以使用CompletionService来实现
CompletionService将ExecutorService和BlockingQueue功能融合在了一起。
你可以将Callable任务提交给它来执行,它执行完返回的Future结果会放进BlockingQueue中。
你再用类似于队列操作的take和poll方法来获得已完成的结果。

public class Test2 {  

    private final ExecutorService executor = Executors.newFixedThreadPool(3);  

    public void runTasks(){  
        CompletionService<String> completionService = new ExecutorCompletionService<String>(executor);  
        completionService.submit(new Callable<String>(){  
            @Override  
            public String call() throws Exception {  
                return "result1";  
            }  
        });  
        completionService.submit(new Callable<String>(){  
            @Override  
            public String call() throws Exception {  
                return "result2";  
            }  
        });  
        completionService.submit(new Callable<String>(){  
            @Override  
            public String call() throws Exception {  
                return "result3";  
            }  
        });  

        for(int i=0;i<3;i++){  
            try {  
                Future<String> f = completionService.take();  
                System.out.println(f.get());  
            } catch (InterruptedException e) {  
            } catch (ExecutionException e) {  
            }  
        }  
    }  

    public static void main(String[] args) {  
        Test2 t = new Test2();  
        t.runTasks();  
    }  
}  

我们可以看到我们使用了一个ExecutorService作为参数来初始化CompletionService。多个CompletionService可以共享一个ExecutorService。因此可以创建一个对于特定计算私有,又能共享一个ExecutorService的应用。

七、线程池的关闭——shutdown()和shutdownNow()方法。

我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同,shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。对shutdown()方法的调用可以防止新的任务被提交给这个线程池,当前线程将继续运行在shutdown()被调用之前提交的所有任务

shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。

只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。

我们加入shutdown()和shutdownNow()方法来完善一下上面这个例子:

void shutdownAndAwaitTermination(ExecutorService pool) {  
   pool.shutdown(); // 防止新的任务被提交上来  
   try {  
     // 等待当前已经存在的任务执行完  
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {  
       pool.shutdownNow(); // 如果过了指定时间还有任务没有完成,立马停止它们  
       // 等待任务响应取消命令  
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))  
           System.err.println("Pool did not terminate");  
     }  
   } catch (InterruptedException ie) {  
     // (Re-)Cancel if current thread also interrupted  
     pool.shutdownNow();  
     // Preserve interrupt status  
     Thread.currentThread().interrupt();  
   }  
 }  

八、线程池的合理配置

一、确定线程数

在工作中,为了加快程序的处理速度,我们需要将问题分解成若干个并发执行的任务。接着我们将这些任务委派给线程,以便使它们可以并发的执行。但是需要注意的是,由于资源所限,我们不能创建过多的线程。
这就涉及到一个 确定创建多少线程数才是合理 的问题。
《java虚拟机并发编程》一书中,对这个问题有详尽的解答,本人在此摘取归纳如下:

1.我们可以先获取到系统可用的处理器核心数:

Runtime.getRuntime().availableProcessors()

2.确定任务的类型:

如果所有任务都是计算密集型的,则创建处理器可用核心数那么多的线程数就可以了。
在这种情况下,创建更多的线程对程序的性能而言反而是不利的。因为当有多个任务处于就绪状态时,处理器核心需要在线程间频繁进行上下文切换,而这种切换对程序性能损耗较大。
如果任务都是IO密集型的,那么我们需要开更多的线程来提高性能。
当一个任务执行IO操作时,其线程被阻塞,于是处理器可以立即进行上下文切换以便处理其他就绪线程。如果我们只有处理器可用核心数那么多线程的话,则即使有待执行的任务也无法处理,因为我们已经拿不出更多的线程供处理器调度了。

3.计算出程序所需的线程数:

首先我们要明白一个概念叫 阻塞系数
如果任务有50%的时间处于阻塞状态,则阻塞系数为0.5。则程序所需的线程数为处理器可用核心数的两倍。如果任务被阻塞的时间少于50%,即这些任务是计算密集型的,则程序所需线程数将随之减少,但最少也不应该低于处理器的核心数。如果任务被阻塞的时间大于执行时间,即该任务是IO密集型的,我们就需要创建比处理器核心数大几倍数量的线程。
我们可以计算出程序所需线程的总数,总结如下:

线程数 = CPU可用核心数/(1 - 阻塞系数),其中阻塞系数的取值在0和1之间。

计算密集型人物的阻塞系数为0,而IO密集型任务的阻塞系数则接近1。

二、线程池的监控:

我们可以通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用
taskCount:线程池需要执行的任务数量。
completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
getActiveCount:获取活动的线程数。
通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:

protected void beforeExecute(Thread t, Runnable r) { }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值