线程池ThreadPoolExecutor的使用以及理解

来源:互联网 发布:mac 建筑 软件 收费吗 编辑:程序博客网 时间:2024/05/16 07:19

java线程池实现类ThreadPoolExecutor

ThreadPoolExecutor可以构造一个线程池,何为线程池,就是装了一堆线程的容器。这些线程可以用来执行任务。其实按道理讲,我们可以自己创建一个线程来执行自己的任务。那么为啥要用线程池呢,是因为线程池装了一堆已经创建好了的线程,不需要我们自己再去创建,这节省了创建线程的资源,也保障了线程的高效利用。

ThreadPoolExcecutor,Thread,Runable,Callable,Future的关系。

ThreadPoolExcecutor线程池中,装的是多个Thread。
Runable,Callable是两个可以交给线程执行的任务,它俩有个区别就是,Callable对应的call方法可以自定义返回值,Runable的run方法没有返回值。
那么Future在这其中又扮演什么角色呢?ThreadPoolExcecutor的submit方法,可以传入Callable任务,也可以传入Runable任务,该方法可以返回Future对象。Future对象可以获取任务的完成情况,Future的isDone()方法可以获取当前任务是否执行完毕,该方法不会阻塞;get()方法可以获取任务执行结果的返回值,注意,get()方法是会阻塞的方法,也就是说当前任务在线程中没有运行完毕,只要程序调用了get()方法,会一直阻塞,等到线程运行完毕之后,才能继续执行下去。

示例

以下示例中包含了ThreadPoolExcecutor、Callable、Future的相关方法的简单使用
陈旭运行结果请看代码后面

import java.util.ArrayList;import java.util.Arrays;import java.util.Iterator;import java.util.List;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.Future;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class ExcutorTest {     /**     * 创建一个Callable任务,目的是:让当前线程睡眠一定时间     *      * @param num     *            睡眠时间     * @return     */    public static Callable<Long> createCallable(final int num) {        return new Callable<Long>() {            @Override            public Long call() throws Exception {                Thread.sleep(num);                return System.currentTimeMillis();            }        };    }    @SuppressWarnings("unchecked")    public static void main(String[] args) {        // 创建线程池        // 参数说明:核心线数,最大线程数,线程失效时间,线程失效时间的单位,等待线程池处理的任务        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 3, 30, TimeUnit.SECONDS,                new LinkedBlockingQueue<Runnable>());        long begin = System.currentTimeMillis();// 记录一下开始时间        try {            // 往线程池放入5个Callable任务            Future<Long> f1 = threadPool.submit(createCallable(2 * 1000));// 此任务执行完毕需要消耗2秒            Future<Long> f2 = threadPool.submit(createCallable(3 * 1000));// 此任务执行完毕需要消耗3秒            Future<Long> f3 = threadPool.submit(createCallable(4 * 1000));// 此任务执行完毕需要消耗4秒            Future<Long> f4 = threadPool.submit(createCallable(5 * 1000));// 此任务执行完毕需要消耗5秒            Future<Long> f5 = threadPool.submit(createCallable(6 * 1000));// 此任务执行完毕需要消耗6秒            long startSucess = System.currentTimeMillis();            System.out.println("所有任务放入完毕:" + startSucess);            Long f5Rst = f5.get();            // 如果下面打印有时间差,则证明get()方法会阻塞            System.out.println("\nf5任务返回数据的时间与所有任务放入完毕的时间差为:" + (System.currentTimeMillis() - startSucess));            System.out.println("\nf5任务返回的数据:" + f5Rst);            System.out.println("\n获取完f5返回值的时间:" + System.currentTimeMillis());            Long f1Rst = f1.get();            Long f2Rst = f2.get();            Long f3Rst = f3.get();            Long f4Rst = f4.get();            // 如果当前时间和获取完f5返回值的时间相同,则证明剩下的几个任务已经在f5之前执行完毕。毕竟f5是耗时最长的任务            System.out.println("\n获取最早执行的任务,对比当前时间,看看会不会阻塞,当前时间为:" + System.currentTimeMillis());            System.out.println("\ntask count:" + threadPool.getTaskCount());            // 如何在有任务没执行完毕之前,保证程序一直阻塞在这儿呢?            // 可以参考下面的代码            // 说明:因为有很多任务在执行或者等待执行,我们也不知道哪个任务耗时最长,所以直接调用Future的get()方法是不靠谱的            //     因此,我们可以把所有任务装在集合中,一直循环判断任务是否执行完毕(Future.isDone()),如果执行完毕,就从集合中移除            //     最后集合为空了,任务肯定就执行完毕了。这段代码解决了我自己遇到的一个问题,所以在这儿记录一下。            //     我看网上部分资料,使用了线程池的ThreadPoolExecutor.shutdown()方法,来保证所有任务执行完毕之前,一直阻塞在那儿            //     但是有一个问题是,调用了ThreadPoolExecutor.shutdown()之后,再往线程池中submit任务,就会抛异常,大家可以试试            //     这导致我的线程池就不能复用了,这太浪费资源了            List<Future<Long>> futures = new ArrayList<Future<Long>>(Arrays.asList(f1, f2, f3, f4, f5));            while (true) {                Iterator<Future<Long>> iterator = futures.iterator();// 获取迭代器                while (iterator.hasNext()) {                    Future<Long> next = iterator.next();                    if (next.isDone()) {// 如果当前任务完成                        iterator.remove();// 则从list中移除                    }                }                if (futures.isEmpty()) {// 如果list为空了,就证明所有任务都执行完毕了                    break;//跳出此while循环,继续执行以下代码                }            }        } catch (InterruptedException e) {            System.out.println("抛异常了,赶快处理");        } catch (ExecutionException e) {            System.out.println("又抛异常了,赶快处理");        }        long end = System.currentTimeMillis();        System.out.println("spend time:" + (end - begin));        System.out.println("处理完毕");    }}

运行结果

所有任务放入完毕:1506674591202

f5任务返回数据的时间与所有任务放入完毕的时间差为:12000

f5任务返回的数据:1506674603202

获取完f5返回值的时间:1506674603202

获取最早执行的任务,对比当前时间,看看会不会阻塞,当前时间为:1506674603202

task count:5
spend time:12001
处理完毕

分析及总结

可以从运行结果中明显看到Future的get()方法是会阻塞的。
另外,执行完毕的时间是12秒,任务耗时最长为6秒,那为啥为总的耗时是12秒呢?
因为呀,我们线程池初始化的时候,核心线程为2个,最大线程为3个,大家可以算一算。