线程池原理(七):线程池的使用

来源:互联网 发布:java方法覆盖输出结果 编辑:程序博客网 时间:2024/06/06 17:10

这篇文章通过介绍线程池的常见用法,总结前面学习到的内容。

主要包括

  1. ThreadPoolExecutor的使用
  2. ScheduledThreadPoolExecutor的使用
  3. 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,将任务的返回值累加并返回。

原创粉丝点击