线程池ThreadPoolExecutor的使用以及理解
2017-09-29 16:56
330 查看
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("处理完毕"); } }
运行结果
所有任务放入完毕:1506674591202f5任务返回数据的时间与所有任务放入完毕的时间差为:12000
f5任务返回的数据:1506674603202
获取完f5返回值的时间:1506674603202
获取最早执行的任务,对比当前时间,看看会不会阻塞,当前时间为:1506674603202
task count:5
spend time:12001
处理完毕
分析及总结
可以从运行结果中明显看到Future的get()方法是会阻塞的。另外,执行完毕的时间是12秒,任务耗时最长为6秒,那为啥为总的耗时是12秒呢?
因为呀,我们线程池初始化的时候,核心线程为2个,最大线程为3个,大家可以算一算。
相关文章推荐
- 对引用和指针使用以及函数返回引用和指针类型的理解
- 对引用和指针使用以及函数返回引用和指针类型的理解
- 加深C# 中字符串前加@符号理解以及使用~~
- 关于js中单引号(')和双引号(")的使用以及转义的理解
- IoC容器Autofac(1) -- 什么是IoC以及理解为什么要使用Ioc
- amor详解 二叉排序树的使用以及理解
- 加深C# 中字符串前加@符号理解以及使用~~
- ThreadPoolExecutor线程池的使用与理解
- 理解C# 4 dynamic(1) - var, object, dynamic的区别以及dynamic的使用
- 任务队列ThreadPoolExecutor线程池的使用与理解
- kill用法详细解释(特别是信号量9的使用以及理解)
- 关于js中单引号(')和双引号(")的使用以及转义的理解
- intellij idea开发android从零开始(二) android的activity的使用以及理解
- 什么是IoC以及理解为什么要使用Ioc
- kill用法详细解释(特别是信号量9的使用以及理解)
- 线程池技术个人理解以及c语言的简单实现
- 对引用和指针使用以及函数返回引用和指针类型的理解
- ThreadPoolExecutor线程池的使用与理解
- 加深C# 中字符串前加@符号理解以及使用~~
- 如何理解、使用Android LogCat以及通过Monkey进行压力测试