Java等待异步线程池跑完再执行指定方法的三种方式(condition、CountDownLatch、CyclicBarrier)

Java等待异步线程池跑完再执行指定方法的三种方式(condition、CountDownLatch、CyclicBarrier)

@Async如何使用

使用@Async标注在方法上,可以使该方法异步的调用执行。而所有异步方法的实际执行是交给TaskExecutor的。

复制代码
1.启动类添加@EnableAsync注解
2. 方法上添加@Async,类上添加@Component

三个异步方法

复制代码
    @Async
    public void doTaskTwo( CyclicBarrier barry) throws Exception {
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(3000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
        barry.await();
    }

    @Async
    public void doTaskThree( CyclicBarrier barry) throws Exception {
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(5000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
        barry.await();
    }

    @Override
    @Async
    public void doTask2One(CountDownLatch count) throws Exception {
        System.out.println("开始做任务1");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(3000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务1,耗时:" + (end - start) + "毫秒");
        count.countDown();
    }

CyclicBarrier

复制代码
@GetMapping("/doTask")
    public void doLogin() throws Exception {

        // 通过它可以实现让一组线程等待至某个状态之后再全部同时执行
        //第一个参数,表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
        //第二个参数,表示用于在线程到达屏障时,优先执行barrierAction这个Runnable对象,方便处理更复杂的业务场景。
        CyclicBarrier barry = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("1111111111111");
            }
        });
        kafkaTopicService.doTaskOne(barry);
        kafkaTopicService.doTaskTwo(barry);
        kafkaTopicService.doTaskThree(barry);


    }

执行结果

复制代码
开始做任务一
开始做任务二
开始做任务三
完成任务一,耗时:1263毫秒
完成任务二,耗时:2508毫秒
完成任务三,耗时:3753毫秒
1111111111111

注意: 接口响应成功时候 后台逻辑还在走
CyclicBarrier 的使用场景也很丰富。

比如,司令下达命令,要求 10 个士兵一起去完成项任务。

这时就会要求 10 个士兵先集合报到,接着,一起雄赳赳,气昂昂地去执行任务当 10 个士兵把自己手上的任务都执行完了,那么司令才能对外宣布,任务完成

CyclicBarrier 比 CountDownLatch 略微强大一些,它可以接收一个参数作为 barrierAction。

所谓 barrierAction 就是当计数器一次计数完成后,系统会执行的动作。

如下构造函数,其中, parties 表示计数总数,也就是参与的线程总数。

复制代码
public CyclicBarrier(int parties, Runnable barrierAction) 


package com.shockang.study.java.concurrent.aqs;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier cyclic;

        Soldier(CyclicBarrier cyclic, String soldierName) {
            this.cyclic = cyclic;
            this.soldier = soldierName;
        }

        public void run() {
            try {
                //等待所有士兵到齐 第一次等待
                cyclic.await();
                doWork();
                //等待所有士兵完成工作 第二次等待
                cyclic.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }

        void doWork() {
            try {
                Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldier + ":任务完成");
        }
    }

    public static class BarrierRun implements Runnable {
        boolean flag;
        int N;

        public BarrierRun(boolean flag, int N) {
            this.flag = flag;
            this.N = N;
        }

        public void run() {
            if (flag) {
                System.out.println("司令:[士兵" + N + "个,任务完成!]");
            } else {
                System.out.println("司令:[士兵" + N + "个,集合完毕!]");
                flag = true;
            }
        }
    }

    public static void main(String args[]) throws InterruptedException {
        final int N = 10;
        Thread[] allSoldier = new Thread[N];
        boolean flag = false;
        CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
        //设置屏障点,主要是为了执行这个方法
        System.out.println("集合队伍!");
        for (int i = 0; i < N; ++i) {
            System.out.println("士兵 " + i + " 报道!");
            allSoldier[i] = new Thread(new Soldier(cyclic, "士兵 " + i));
            allSoldier[i].start();
        }
    }
}

控制台输出

复制代码
集合队伍!
士兵 0 报道!
士兵 1 报道!
士兵 2 报道!
士兵 3 报道!
士兵 4 报道!
士兵 5 报道!
士兵 6 报道!
士兵 7 报道!
士兵 8 报道!
士兵 9 报道!
司令:[士兵10个,集合完毕!]
士兵 0:任务完成
士兵 3:任务完成
士兵 6:任务完成
士兵 4:任务完成
士兵 9:任务完成
士兵 8:任务完成
士兵 2:任务完成
士兵 5:任务完成
士兵 7:任务完成
士兵 1:任务完成
司令:[士兵10个,任务完成!]

说明

上述代码第 65 行创建了 CyclicBarrier 实例,并将计数器设置为 10 ,要求在计数器达到指标时,执行第 51 行的 run() 方法。

每一个士兵线程都会执行第 18 行定义的 run() 方法。

在第 24 行,每一个士兵线程都会等待,直到所有的士兵都集合完毕。

集合完毕意味着 CyclicBarrier 的一次计数完成,当再一次调用 CyclicBarrier.await() 方法时,会进行下一次计数。

第 22 行模拟了士兵的任务。

当一个士兵任务执行完,他就会要求 CyclicBarrier 开始下次计数,这次计数主要目的是监控是否所有的士兵都己经完成了任务。

一旦任务全部完成,第 42 行定义的 BarrierRun 就会被调用,打印相关信息。

2、CountDownLatch

复制代码
 @GetMapping("/doTask2")
    public void doTask2() throws Exception {

        CountDownLatch count = new CountDownLatch(3);

        kafkaTopicService.doTask2One(count);
        kafkaTopicService.doTask2Two(count);
        kafkaTopicService.doTask2Three(count);
        count.await();
        System.out.println("11111111111111111");

    }

执行结果:

复制代码
开始做任务1
开始做任务二
开始做任务3
完成任务1,耗时:179毫秒
完成任务3,耗时:1829毫秒
完成任务二,耗时:2376毫秒
11111111111111111

注意: 接口响应成功时候 后台逻辑已经走完

1、juc中condition接口提供的await、signal、signalAll方法,需配合lock

复制代码
List<Integer> villageList = new ArrayList<>();
    List<Integer> villageList2 = new ArrayList<>();
    villageList.add(1);
    villageList.add(2);
    villageList.add(3);
    ExecutorService threadPool = Executors.newFixedThreadPool(2);

    Lock lock = new ReentrantLock();
    Condition cond = lock.newCondition();
    for(int flag = 0;flag<villageList.size();flag++){
        Integer i = villageList.get(flag);
        threadPool.execute(() -> {
            try {
                villageList2.add(i);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if(villageList2.size() == villageList.size()){
                lock.lock();
                cond.signal();
                lock.unlock();
            }
        });
    }
    lock.lock();
    try {
        cond.await(5, TimeUnit.SECONDS);
        lock.unlock();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(villageList2.size());
    threadPool.shutdown();
}

CountDownLatch和CyclicBarrier的比较

1.CountDownLatch是减计数方式,countDown()方法只参与计数不会阻塞线程,而CyclicBarrier是加计数方式,await()方法参与计数,会阻塞线程。

2.CountDownLatch计数为0无法重置,而CyclicBarrier计数达到初始值,则可以重置,因此CyclicBarrier可以复用。

附:CountDownLatch有发令枪的效果,可以用来并发测试

复制代码
public class test2 {

    private final static CountDownLatch CountDownLatch=new CountDownLatch(100);

    public static void main(String[] args) throws Exception{

        for (int i=0;i<100;i++){
            new Thread(() -> {
                try {
                    CountDownLatch.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                getUUID();
            }).start();
            CountDownLatch.countDown(); //如果减到0 统一出发,发枪开炮

        }
    }

    public static void getUUID(){
        UUID uuid= UUID.randomUUID();
        System.out.println(uuid);
    }
}

注意事项

  1. CountDownLatch 对象的计数器只能减不能增,即一旦计数器为 0,就无法再重新设置为其他值,因此在使用时需要根据实际需要设置初始值。

  2. CountDownLatch 的计数器是线程安全的,多个线程可以同时调用 countDown() 方法,而不会产生冲突。

  3. 如果 CountDownLatch 的计数器已经为 0,再次调用 countDown() 方法也不会产生任何效果。

  4. 如果在等待过程中,有线程发生异常或被中断,计数器的值可能不会减少到 0,因此在使用时需要根据实际情况进行异常处理。

当worker1线程由于异常没有执行countDown()方法,最后state结果不为0,导致所有线程停在AQS中自旋(死循环)。所以程序无法结束。(如何解决这个问题呢?请看案例二)

复制代码
// 等待 3 个线程完成任务
if (!latch.await(5, TimeUnit.SECONDS)) {
    LOGGER.warn("{} time out", worker1.name);
}
 
// 所有线程完成任务后,执行下面的代码
LOGGER.info("all workers have finished their jobs!");
  1. CountDownLatch 可以与其他同步工具(如 Semaphore、CyclicBarrier)结合使用,实现更复杂的多线程同步。
相关推荐
爱编程的鱼6 分钟前
OpenCV Python 绑定:原理与实战
c语言·开发语言·c++·python
这周也會开心13 分钟前
云服务器安装JDK、Tomcat、MySQL
java·服务器·tomcat
hrrrrb1 小时前
【Spring Security】Spring Security 概念
java·数据库·spring
小信丶1 小时前
Spring 中解决 “Could not autowire. There is more than one bean of type“ 错误
java·spring
sdgsdgdsgc1 小时前
Next.js企业级应用开发:SSR、ISR与性能监控方案
开发语言·前端·javascript
周杰伦_Jay2 小时前
【Java虚拟机(JVM)全面解析】从原理到面试实战、JVM故障处理、类加载、内存区域、垃圾回收
java·jvm
rit84324995 小时前
基于MATLAB的模糊图像复原
开发语言·matlab
fie88895 小时前
基于MATLAB的声呐图像特征提取与显示
开发语言·人工智能
程序员小凯6 小时前
Spring Boot测试框架详解
java·spring boot·后端
豐儀麟阁贵6 小时前
基本数据类型
java·算法