Java并发执行任务的几种方式

背景

在编写业务代码时经常遇到并发执行多个任务的需求,因为串行执行太慢,会影响业务代码性能。特别对于直接面向普通用户的业务来说用户体验至关重要,保证用户体验重要的一点是要“快”。业务代码中经常需要调用其它业务接口或者同时从多个数据源取数据再处理等,这种情况下势必要走网络请求,网络消耗必不可少,最好的情况是毫秒级别,一般情况下是几十毫秒级别,甚至几百毫秒,TimeoutException恐怕大家并不陌生。

例子

当你浏览微信朋友圈时,微信会把你朋友最近的动态展示在你朋友圈里,并且按照时间顺序最近的排在前面。你会发现,朋友圈里不会展示把你删除了或者你把他删除了的好友,不会展示被你设置过“不看他(她)的朋友圈”的好友或者对方把你设置过“不让他(她)看我的朋友圈”的好友,不会展示被你拉黑或把你拉黑的好友,不会展示被微信系统标记为spam的好友,等等。对于微信这种支持数亿人聊天的应用,其系统必定很复杂,解耦做得也比较好。
下面模拟下这个接口的实现:

    public List<Feed> lastestFeeds(String wxId) {
                //串行执行
        //1. 获取你得好友列表
        //2. 去掉把你删除的好友
        //3. 去掉被你删除的好友
        //4. 去掉被你设置过"不看他(她)的朋友圈"的好友
        //5. 去掉对你设置过"不让他(她)看我的朋友圈"的好友
        //6. 去掉被你拉黑和把你拉黑的好友
        //7. 去掉被微信系统标记为作弊的好友
        //...
                //8. 获取好友最近动态再返回
    }

对于微信这种复杂的系统,通常不可能从一个接口获取到这些信息,必须从多个接口获取到这些信息后再处理。如果说串行实现这些功能,你可以想象一下是不是慢到吐血,相信微信也不会这么干,否则朋友圈会刷半天也没响应,那么这个用户体验就太糟糕了。那么这个时候并发执行这些子任务就可以很高效的处理掉这种情况。具体到这个接口也就是会把1-7拆解成单个子任务,再丢到线程池异步的执行。最后执行完了,再汇总处理。

    public List<Feed> lastestFeedsV2(String wxId) {
        //并发运行,无先后先后执行
        //1. 获取你得好友列表
        //2. 去掉把你删除的好友
        //3. 去掉被你删除的好友
        //4. 去掉被你设置过"不看他(她)的朋友圈"的好友
        //5. 去掉对你设置过"不让他(她)看我的朋友圈"的好友
        //6. 去掉被你拉黑和把你拉黑的好友
        //7. 去掉被微信系统标记为作弊的好友
        //...
        //等待所有子任务完成,汇总处理
        //8. 获取好友最近动态再返回
    }

那么如何实现并发运行呢,下面讨论几种实现。

CountDownLatch.await() VS ExecutorService.invokeAll()

从功能上讲这两者均可以实现并发执行多个任务并等待的功能。先看代码如何完成以上功能:
CountDownLatch实现:

    public List<Feed> lastestFeeds(String wxId) {
        ThreadPoolExecutor executor = ...;
        CountDownLatch latch = new CountDownLatch(7);
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try{
                    //1. 获取你得好友列表
                } catch (Exception e) {

                } finally {
                    latch.countDown();
                }
            }
        });

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try{
                    //2. 去掉把你删除的好友
                } catch (Exception e) {

                } finally {
                    latch.countDown();
                }
            }
        });

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try{
                    //3. 去掉被你删除的好友
                } catch (Exception e) {

                } finally {
                    latch.countDown();
                }
            }
        });

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try{
                    //4. 去掉被你设置过"不看他(她)的朋友圈"的好友
                } catch (Exception e) {

                } finally {
                    latch.countDown();
                }
            }
        });

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try{
                    //5. 去掉对你设置过"不让他(她)看我的朋友圈"的好友
                } catch (Exception e) {

                } finally {
                    latch.countDown();
                }
            }
        });

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try{
                    //6. 去掉被你拉黑和把你拉黑的好友
                } catch (Exception e) {

                } finally {
                    latch.countDown();
                }
            }
        });

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try{
                    //7. 去掉被微信系统标记为作弊的好友
                } catch (Exception e) {

                } finally {
                    latch.countDown();
                }
            }
        });
        try {
            //latch.await();
            latch.await(500, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //等待所有子任务完成,汇总处理
        //8. 获取好友最近动态再返回
    }

ExecutorService实现:

    public List<Feed> lastestFeeds(String wxId) {
        ThreadPoolExecutor executor = ...;
        CountDownLatch latch = new CountDownLatch(7);
        List<Callable<String>> tasks = new ArrayList<>(7);
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //1. 获取你得好友列表
                return ...;
            }
        });
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //2. 去掉把你删除的好友
                return ...;
            }
        });
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //3. 去掉被你删除的好友
                return ...;
            }
        });
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //4. 去掉被你设置过"不看他(她)的朋友圈"的好友
                return ...;
            }
        });
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //5. 去掉对你设置过"不让他(她)看我的朋友圈"的好友
                return ...;
            }
        });
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //6. 去掉被你拉黑和把你拉黑的好友
                return ...;
            }
        });
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //7. 去掉被微信系统标记为作弊的好友
                return ...;
            }
        });
        try {
//          List<Future<String>> futureList = executor.invokeAll(tasks);
            List<Future<String>> futureList = executor.invokeAll(tasks, 500, TimeUnit.MILLISECONDS);
            for(Future<String> future : futureList) {
                if(future.isCancelled()) {
                    //处理
                }
                try {
                    future.get();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

区别

两者实现的区别是什么?从性能上讲应该是没有太大差异,在此要感谢Doug Lea大神,感谢他优雅的实现,具体的实现,请各位看官自行百度和google。
我主要是谈谈这两者使用上的细微差异,可以根据具体业务场景选择一个更合适的。两者在等待子任务结束时,均提供了限时和不限时版本。

如果使用限时版本

关键的差异在于CountDownLatch超时后不再阻塞主线程,而是会继续执行,对于没有完成的子任务,它不知道也不会处理,也就是说没有完成的子任务还是会继续执行。而ExecutorService.invokeAll超时后,会取消所有在线程池任务队列中等待运行的子任务,说白了就是超时的子任务会被取消。
对于我举的微信这个例子来说,主线程等待超时后,子任务再返回数据是没有意义的,继续执行超时的子任务只会浪费CPU而已,尤其是对于QPS较大的业务来说,影响更明显。这种情况下选择ExecutorService.invokeAll可能更好些,当然如果你提供额外的状态标识如定义个AtomicBoolean来辅助实现超时不执行子任务的功能也是可以的。当然有些场景,即便主线程超时了,子任务也必须执行(例如子任务中涉及数据存储,否则数据可能丢失),这种情况下,应该是使用CountDownLatch。
希望读者诸君能够认真体会这点差异。

对于不限时版本

两者无太大差异,但在生产环境中要慎用,因为它不可控。

其它

另外再一点要注意的是,使用CountDownLatch时子任务的结果需要通过线程安全的容器收集如ConcurrentHashMap等,再处理。ExecutorService.invokeAll因为返回的是List,所以可以拿到结果,但如果想通过Future找到是哪个子任务,就只能根据顺序来确认了。

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 答:Java实现定时任务方式有三种:1.使用java.util.Timer类;2.使用java.util.concurrent.ScheduledThreadPoolExecutor类;3.使用Quartz框架。 ### 回答2: Java实现定时任务有以下几种方式: 1. Timer类:Java提供了Timer类来帮助实现定时任务。通过创建一个Timer实例和一个TimerTask实例,可以设定任务执行时间和间隔时间,然后使用Timer的schedule()方法来启动任务。 2. ScheduledExecutorService接口:Java提供了ScheduledExecutorService接口来实现定时任务。该接口继承自ExecutorService接口,可以使用ThreadPoolExecutor来实现。通过调用schedule()方法,可以设定任务执行时间和间隔时间,然后将任务提交给ScheduledExecutorService。 3. cron表达式:在Java中,还可以使用cron表达式来实现定时任务。cron表达式是一种用来设置时间的字符串格式,通过设置不同的字段,可以实现精确到秒的定时任务。可以使用Quartz框架等工具来解析和执行cron表达式。 4. Spring框架的@Scheduled注解:如果在Spring框架中开发应用,可以使用@Scheduled注解来实现定时任务。通过在方法上标记@Scheduled注解,并设置相应的时间表达式,可以让方法在指定的时间间隔内执行。 需要注意的是,以上方式都是基于Java的定时任务实现,可以根据具体需求选择最合适的方式来实现定时任务。 ### 回答3: 在Java中,我们可以使用以下几种方式来实现定时任务。 1. Timer类:Java中的Timer类是一个简单的定时器工具,它可以通过创建Timer对象并调用其schedule()方法来设置定时任务。该方法可以指定一个任务(实现了TimerTask接口的类)和一个延迟时间,然后在延迟时间之后开始执行定时任务。 2. ScheduledExecutorService接口:Java中的ScheduledExecutorService接口是一个在指定的延迟时间之后或者以固定的时间间隔重复执行任务的调度器。可以使用Executors工厂类的newScheduledThreadPool()方法创建ScheduledExecutorService对象,并使用其schedule()方法来设置定时任务。 3. Quartz框架:Quartz是一个功能强大且灵活的开源定时任务调度框架。它提供了许多高级的调度功能,如任务并发执行、动态调度、集群支持等。使用Quartz框架,我们可以通过配置定时任务的详细信息(如触发器、调度器等),然后让框架来管理和执行定时任务。 4. Spring的Task:Spring框架提供了一个简单的任务调度器,可以通过配置的方式实现定时任务。在Spring的配置文件中,我们可以使用<task:scheduler>和<task:scheduled>标签来定义定时任务的调度器和具体的定时任务方法。 综上所述,Java实现定时任务方式有多种选择。根据需求的复杂性和灵活性的要求,我们可以选择适合的方式来实现定时任务
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值