线程池原理(七):线程池的使用
来源:互联网 发布:java方法覆盖输出结果 编辑:程序博客网 时间:2024/06/06 17:10
这篇文章通过介绍线程池的常见用法,总结前面学习到的内容。
主要包括
- ThreadPoolExecutor的使用
- ScheduledThreadPoolExecutor的使用
- ExecutorCompletionService的使用
1. 统计某个区间内数字和
这是一个常见的问题,比如计算1到1000000之间的数字和,通常情况下,一个线程去遍历计算即可,既然学到了多线程,我们可以将该任务切割,分成多个子任务并交给线程池来解决,子任务解决后将这些任务的结果归并,就可以得到我们要的值了。虽然这个问题很简单,但是它的解决思路可以用于解决其他类似的问题。
先看下面代码:
public class AsyncEngine { /** * 线程池的线程数量不需要太多,它是和CPU核数量有关,太多反而上下文切换效率不好 */ private static ExecutorService executorService = new ThreadPoolExecutor(2, 10, 30, TimeUnit.SECONDS, new SynchronousQueue()); /** * 定时任务线程池 */ private static ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(2); /** * 完成服务线程池 */ private static CompletionService completionService = new ExecutorCompletionService(executorService); /** * 异步执行任务,所有任务执行完成后该方法才返回,如果任务执行失败,该任务返回值为null * @param tasks 待执行的任务 * @param <T> 任务的返回值 * @return */ public static <T> List<T> concurrentExecute(List<Callable<T>> tasks) { if (tasks == null || tasks.size() == 0) { return Lists.newArrayList(); } List<T> res = Lists.newArrayList(); try { List<Future<T>> futures = executorService.invokeAll(tasks); for (Future<T> future : futures) { T t = null; try { t = future.get(); } catch (ExecutionException e) { e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } res.add(t); } } catch (InterruptedException e) { e.printStackTrace(); } return res; } /** * 定时执行任务 * @param runnable 执行的任务 * @param initialDelay 初始延时 * @param delay 任务间的等待时间 * @param unit 时间单位 * @return */ public static ScheduledFuture<?> scheduledWithFixedDelay(Runnable runnable, long initialDelay, long delay, TimeUnit unit) { return scheduledExecutorService.scheduleWithFixedDelay(runnable, initialDelay, delay, unit); } /** * 完成服务 * @param tasks * @param <T> * @return */ public static <T> CompletionService completionExecute(List<Callable<T>> tasks) { if (tasks == null || tasks.size() == 0) { return completionService; } for (Callable<T> task : tasks) { completionService.submit(task); } return completionService; }}
这段代码是在项目中实际使用的代码,只是作了部分改动,它是一个异步执行引擎,方法concurrentExecute并发执行任务,当这些任务执行完成后,该方法才会返回。我们定义了一个线程池,指定核心线程数为2,最大线程数为10,线程数定义太大无意义,因为它是和CPU核数是有关系的。
concurrentExecute方法中,调用线程池的invokeAll方法,该方法返回Future的列表,然后遍历该列表,Future的get方法会阻塞,直到该任务执行完成,将任务返回结果放入List中。注意,当任务抛出异常时,返回结果为null,该null值也一并放到List中作为concurrentExecute方法的返回值。
有了这个异步执行引擎,我们继续看下怎么实现统计某个区间内的数字和,看下代码:
public class Sum { /** * 多线程并发计算 * @param min * @param max * @return */ public static long sum1(int min, int max) { if (min > max) { return 0L; } List<Callable<Long>> tasks = Lists.newArrayList(); while (min < max) { final int left = min; final int right = max; //分割任务,每个任务最多只相加1000个数 Callable<Long> task = new Callable<Long>() { @Override public Long call() throws Exception { long sum = 0; int r = (left + 1000) < right ? (left + 1000) : right; for (int i = left; i < r; i++) { sum += i; } return sum; } }; tasks.add(task); min += 1000; } //异步执行,执行完成后该方法返回 List<Long> res = AsyncEngine.concurrentExecute(tasks); long sum = 0; //归并结果 for (Long count : res) { sum += count; } return sum; } /** * 单线程计算 * @param min * @param max * @return */ public static long sum2(int min, int max) { long sum = 0; for (int i = min; i < max; i++) { sum += i; } return sum; } public static void main(String[] args) { System.out.println(sum1(4, 9999)); System.out.println(sum2(4, 9999)); }}
并发统计和的重点在于分割任务,每个任务最多相加1000个数字。子任务分割完成后,一并交给异步执行引擎处理,返回一个列表,该列表保存了每个任务的返回值,最后归并这个列表即可。这段代码也给出了单线程的代码,便于对比。
2. 定时任务
这里的定时任务很简单,5秒过后开始执行任务,任务执行完成等待1秒再次执行该任务,模拟闹钟的秒针运行,每秒嘀嗒一次。上面讲到的异步引擎AsyncEngine已经写好了定时相关的代码,直接运行目标任务即可。
public class Alarm { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { System.out.println("da"); } }; //定时调度,模拟时钟的秒针运行 AsyncEngine.scheduledWithFixedDelay(runnable, 5, 1, TimeUnit.SECONDS); }}
3. 完成服务
利用CompletionService来实现生产者消费者模式,对于第一个例子中讲到的统计区间数字和,主线程其实就是一个消费者,线程池中的线程是生产者,这些生产者计算各个任务,消费者等待这些任务执行结束。
这里面的问题是,通过concurrentExecute方法执行任务,需要等到所有任务执行结束该方法才能返回,如果某些任务已经完成,消费者能够即时的消费,显然更加合理。因此我们在AsyncEngine里添加了completionExecute方法,这个方法提交所有的任务到线程池,返回CompletionService。消费者(主线程)通过CompletionService的take方法取任务Future,取到的Future肯定是已经结束的任务,当take方法阻塞,说明还没有已经结束的任务。看下Sum类的实现:
public class Sum { /** * 多线程并发计算 * @param min * @param max * @return */ public static long sum1(int min, int max) { if (min > max) { return 0L; } List<Callable<Long>> tasks = Lists.newArrayList(); while (min < max) { final int left = min; final int right = max; //分割任务,每个任务最多只相加1000个数 Callable<Long> task = new Callable<Long>() { @Override public Long call() throws Exception { long sum = 0; int r = (left + 1000) < right ? (left + 1000) : right; for (int i = left; i < r; i++) { sum += i; } return sum; } }; tasks.add(task); min += 1000; } //使用CompletionService执行任务 CompletionService<Long> completionService = AsyncEngine.completionExecute(tasks); long sum = 0; for ( int i = 0; i < tasks.size(); i++) { try { sum += completionService.take().get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } return sum; }}
这个类和前面那个Sum类非常类似,这里只是将任务提交给了CompletionService执行,主线程通过CompletionService的take方法取任务的Future,将任务的返回值累加并返回。
- 线程池原理(七):线程池的使用
- Java并发编程系列(七)---- 线程池的使用
- 线程池的原理
- 线程池的原理
- 线程池的原理?
- 线程池的原理
- 线程池的原理
- java 线程池的原理以及使用
- java线程池的原理和使用
- 线程池原理与使用
- 线程池原理及使用
- 线程池的原理及使用(转载)
- Java线程(七)-线程的调度
- 线程池的工作原理
- 理解线程池的原理
- 线程池的实现原理
- 线程池的工作原理
- 理解线程池的原理
- C++ 标签
- SpringMVC日期类型转换问题三大处理方法归纳
- ICPC新疆赛区-Banana
- 数据结构队的使用
- 1024. 科学计数法 (20)
- 线程池原理(七):线程池的使用
- web前端笔试准备1
- Omnet ++ Error in Module (Node) No gate Index specified when access vector gate ‘out';
- 转载—Java中try catch finally语句中含有return语句的执行情况(总结版)
- 【硅谷问道】对于 iOS 开发,人工智能意味着什么?
- 对给定正整数数列A进行重新排序,使得数列A满足所有的A[i]*A[i+1]都是4的倍数。
- 十连测 D2T2 market(二分+背包)
- HDU 2955 Robberies
- Leetcode493——Reverse Pairs