zoukankan      html  css  js  c++  java
  • ExecutorService小试牛刀

    现在的项目中有将学生批量加入课程的需求,于是想根据这个需求测试一下ExecutorService的效率。假设一个场景:现在有100门课,1500名学生,要求每15个人加入一门课程,不重复。

    查询并拼接数据:先从mysql中查询出100门课的id,再拿出1500个学生,然后将1500个学生每15人一组,分成100组,对应100门课程,方法如下:

    1. public List<List<MessagePojo>> getGroupStudent(){
    2. List<Long> courseIds = addUserMapper.find100CourseIds();//拿出100门课的id List
    3. List<MessagePojo> students = addUserMapper.find1500students(); //拿出1500个学生 List
    4. List<List<MessagePojo>> groupStudent = new ArrayList<>();
    5. //对学生分组 没15个学生一组 供100组
    6. if(students!=null&&students.size()>0){
    7. int maxPoint = 15;
    8. int part = students.size()/maxPoint;
    9. System.out.println("共有 : "+students.size()+"条,!"+" 分为 :"+part+"批,!"+" 每批 :"+maxPoint+"个");
    10. for(int i = 1;i <=part ;i++){
    11. List<MessagePojo> temp = students.subList(maxPoint*(i-1),maxPoint*i);
    12. System.out.println(i);
    13. groupStudent.add(temp);
    14. }
    15. }
    16. for (int i=0;i<100;i++) {
    17. List<MessagePojo> temp = groupStudent.get(i);
    18. for (MessagePojo messagePojo : temp) {
    19. messagePojo.setCourseId(courseIds.get(i));
    20. }
    21. }
    22. return groupStudent;
    23. }

    下面这个是不使用executorSevice的插入方法,为了体现出时间差距,总共有三个逻辑比较复杂的方法,因为涉及项目结果,这里就不贴出细则了:

    1. public int doAdd(List<List<MessagePojo>> list){
    2. for (List<MessagePojo> messagePojos : list) {
    3. if (messagePojos.size()>0) {
    4. //学生和课程关系表中加入数据
    5. addUserMapper.method1(messagePojos);
    6. //初始化学生信息到总成绩表
    7. method2(messagePojos);
    8. //spoc课程表中加入学生信息(json)
    9. List<MessagePojo> classes = method3(messagePojos.get(0).getCourseId(),"save");
    10. }
    11. }
    12. return 1;
    13. }

    下面这个是使用了ExecutorService的插入方法:

    1. public int doAddByExecutor(List<List<MessagePojo>> list){
    2. ExecutorService executorService = Executors.newFixedThreadPool(list.size());
    3. try{
    4. for (List<MessagePojo> messagePojos : list) {
    5. executorService.submit(() ->{
    6. //学生和课程关系表中加入数据
    7. addUserMapper.method1(messagePojos);
    8. //初始化学生信息到总成绩表
    9. method2(messagePojos);
    10. //spoc课程表中加入学生信息(json)
    11. List<MessagePojo> classes = method3(messagePojos.get(0).getCourseId(),"save");
    12. });
    13. }
    14. }catch(Exception e){
    15. e.printStackTrace();
    16. }finally {
    17. executorService.shutdownNow();
    18. }
    19. return 1;
    20. }

    首先使用Executors类的静态方法newFixedThreadPool(int size)创建了线程池,其大小为list.size(),其实这里就是100,因为将学生分成了100组。然后使用executorService的submit方法执行任务,其中传入的是一个Runnable的实例,这里使用了Lambda表达式。必须要注意的是,submit方法一定要try,否则容易造成死锁,并且执行完成之后一定要shutdown掉。

    我这里写了两个简单的方法,测试了一下两种插入方法的效率:

    首先是普通方法:耗时2秒左右

    然后是多线程方法:耗时33毫秒。

    这个数据量其实不算很大,但是用多线程来跑还是能看出来耗时差距的。试想一下,如果数据量翻十倍甚至百倍万倍的时候,效率差距就出来了。

    这只是ExecutorService最简单的用法。了解一下源码,先看最重要的submit方法,其中有三个重载:

    Future<?> submit(Runnable task);
    <T> Future<T> submit(Runnable task, T result);
    <T> Future<T> submit(Callable<T> task);

    submit()方法可以接受Runnable和Callable对象,返回Future对象。

    再看execute方法:

    void execute(Runnable command);

    execute是Executor接口的方法,而ExecutorService继承自Executor。executor方法只能接受Runnable对象,没有返回值。

    再来看一下ExecutorService的关闭。

    void shutdown();
    List<Runnable> shutdownNow();

    二者的区别在于,shoudown()方法在终止前允许执行以前提交的任务,而shoutdownNow()方法阻止等待任务启动并试图停止当前正在执行的任务。通俗一点讲,shutdown()方法只是将线程池的状态设置为SHUTDOWN状态,正在执行的任务会继续执行下去,没有被执行的则不能在执行。而shutdownNow()方法则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行的任务则返回。举一个很经典的猴子(各个线程)吃玉米(任务)的例子,假如猴子们正在非常开心的吃玉米,突然接到shutdown指令,那么猴子们则会把手上的玉米吃完,没有拿到手里的地上的玉米则不能再吃。而如果接到shutdownNow指令,这些猴子们立即停止吃玉米,手上的玉米也要放下,地上的更不能吃。(当然,假设猴子是听话的)

    一般情况下,分两个阶段来关闭ExecutorService。首先调用shutdown()方法拒绝传入新任务。其次,如果有必要的话再调用shutdownNow方法取消所有遗留的任务。

    上面讲到了,submit方法可以接受Runnable和Callable,这里讲一下这两者之间的区别。

    我们都知道Runnbale接口只有一个run()方法,并且没有返回值。比如上面插入学生的操作,Lambda中写的代码并不关心返回值,所以这里使用Runnable就可以。但是如果我们需要关注任务线程的返回结果,就要使用Callable了,比如下面这段代码:

    1. TaskWork taskwork = new TaskWork();
    2. Future<RetureData> future = executorService.submit(taskwork);

    其中TaskWork类实现了Callable接口,重写call方法。

    1. public class TaskWork implements Callable<ReturnData>{
    2. @Override
    3. public ReturnData call(){
    4. ReturnData returnData = new ReturnData();
    5. //todo
    6. return returnData;
    7. }
    8. }

    Callable接口有一个泛型,实现时可以随意指定,并且call方法的返回值就是该泛型。在executorService.submit(taskwork)之后,会返回Future的实例,其中的泛型和Callable中的一致。然后通过Future的get方法可以获取返回的具体对象:

    RetureData returnData = future.get();
    原文地址:https://blog.csdn.net/hz_940611/article/details/81119764
  • 相关阅读:
    Git的commit your changes or stash them before you can merge
    php面试题汇总一(基础篇附答案)
    php面试题汇总二(基础篇附答案)
    php面试题汇总三(基础篇附答案)
    php面试题汇总四(基础篇附答案)
    nodejs 后台服务启动
    解决failed to push some refs to
    读书计划
    spring cloud 学习
    spring IOC
  • 原文地址:https://www.cnblogs.com/jpfss/p/11142957.html
Copyright © 2011-2022 走看看