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

本文介绍如何使用线程池处理计算密集型任务,包括ThreadPoolExecutor、ScheduledThreadPoolExecutor和ExecutorCompletionService的使用方法,并给出统计数字和及定时任务的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

主要包括

  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,将任务的返回值累加并返回。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值