Java并发实例之CyclicBarrier的使用

 更新时间:2017年11月15日 12:00:25   作者:生活半篇记  
这篇文章主要介绍了Java并发实例之CyclicBarrier的使用,涉及CyclicBarrier的介绍,以及相关的并发实例,具有一定参考价值,需要的朋友可以了解下。

最近一直整并发这块东西,顺便写点Java并发的例子,给大家做个分享,也强化下自己记忆,如果有什么错误或者不当的地方,欢迎大家斧正。

CyclicBarrier是一种多线程并发控制实用工具,和CountDownLatch非常类似,它也可以实现线程间的计数等待,但是它的功能比CountDownLatch更加复杂且强大。

CyclicBarrier的介绍

CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法。

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties, Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction这个Runnable对象,方便处理更复杂的业务场景。

public CyclicBarrier(int parties) {
  this(parties, null);
}
public int getParties() {
  return parties;
}

实现原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用CyclicBarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await方法返回,再从CyclicBarrier的await方法中返回。

CyclicBarrier主要用于一组线程之间的相互等待,而CountDownLatch一般用于一组线程等待另一组些线程。实际上可以通过CountDownLatch的countDown()和await()来实现CyclicBarrier的功能。即 CountDownLatch中的countDown()+await() = CyclicBarrier中的await()。注意:在一个线程中先调用countDown(),然后调用await()。

构造函数CyclicBarrier可以理解为循环栅栏,这个计数器可以反复使用。比如,假设我们将计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,然后接着凑齐下一批10个线程,这就是它的内在含义。

LOL和王者荣耀的玩家很多,许多人应该都有打大龙的经历,话说前期大家打算一起去偷大龙,由于前期大家都比较弱,需要五个人都齐了才能打大龙,这样程序该如何实现呢?本人很菜,开始我的代码是这么写的(哈哈大家不要纠结我的时间):

public class KillDragon {
	/**
   * 模拟打野去打大龙
   */
	public static void dayePlayDragon(){
		System.out.println("打野在去打大龙的路上,需要10s");
	}
	/**
   * 模拟上单去打大龙
   */
	public static void shangdanPlayDragon(){
		System.out.println("上单在去打大龙的路上,需要10s");
	}
	/**
   * 模拟中单去打大龙
   */
	public static void zhongdanPlayDragon(){
		System.out.println("中单在去打大龙的路上,需要10s");
	}
	/**
   * 模拟ADC和辅助去打大龙
   */
	public static void adcAndFuzhuPlayDragon(){
		System.out.println("ADC和辅助在去打大龙的路上,需要10s");
	}
	/**
   * 模拟大家一起去打大龙
   */
	public static void killDragon()
	  {
		System.out.println("打大龙...");
	}
	public static void main(String[] args)
	  {
		dayePlayDragon();
		shangdanPlayDragon();
		zhongdanPlayDragon();
		adcAndFuzhuPlayDragon();
		killDragon();
	}

结果如下:

打野在去打大龙的路上,需要10s
上单在去打大龙的路上,需要10s
中单在去打大龙的路上,需要10s
ADC和辅助在去打大龙的路上,需要10s
打大龙...

 这完了,大家在路上的时间就花了40s了,显然是错误的。要是都这么干,对方把你塔都要偷光了。不行得改进下,怎么改呢,多线程并发执行,如是我改成了下面这样的,用volatile关键字。

private static volatile int i = 4;
  public static void main(String[] args) {
    new Thread(new Runnable() {
      @Override
      public void run() {
        long start = System.currentTimeMillis();
        while (i!=0){

        }
        while (i==0) {
          killDragon();
          i--;
          long t = System.currentTimeMillis() - start;
          System.out.println("总共耗时:"+t+"毫秒");
        }
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {
        dayePlayDragon();
        try {
          Thread.sleep(10000);
          i--;
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {
        shangdanPlayDragon();
        try {
          Thread.sleep(10000);
          i--;
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {
        zhongdanPlayDragon();
        try {
          Thread.sleep(10000);
          i--;
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {
        adcAndFuzhuPlayDragon();
        try {
          Thread.sleep(10000);
          i--;
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();
  }

结果如下:

打野在去打大龙的路上,需要10s
上单在去打大龙的路上,需要10s
中单在去打大龙的路上,需要10s
ADC和辅助在去打大龙的路上,需要10s
打大龙...
总共耗时:10005毫秒

结果似乎还不错,但是处理起来实在是有点麻烦,需要 while (i!=0)一直在那循环着。这时候学到了用 CyclicBarrier来处理,代码如下:

public static void main(String[] args) {
    CyclicBarrier barrier = new CyclicBarrier(5);
    new Thread(new Runnable() {
      @Override
      public void run() {
        long start = System.currentTimeMillis();
        try {
          barrier.await();
        } catch (InterruptedException e) {
          e.printStackTrace();
        } catch (BrokenBarrierException e) {
          e.printStackTrace();
        }
        killDragon();
        long t = System.currentTimeMillis() - start;
        System.out.println("总共耗时:"+t+"毫秒");
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {
        dayePlayDragon();
        try {
          Thread.sleep(10000);
          barrier.await();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {
        shangdanPlayDragon();
        try {
          Thread.sleep(10000);
          barrier.await();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {
        zhongdanPlayDragon();
        try {
          Thread.sleep(10000);
          barrier.await();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {
        adcAndFuzhuPlayDragon();
        try {
          Thread.sleep(10000);
          barrier.await();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }).start();
  }

大家都没到达之前都等待,结果如下:

打野在去打大龙的路上,需要10s
上单在去打大龙的路上,需要10s
中单在去打大龙的路上,需要10s
ADC和辅助在去打大龙的路上,需要10s
打大龙...
总共耗时:10002毫秒

CyclicBarrier相当于线程的计数器:

CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。

CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。

CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

当然这样使用CyclicBarrier和使用CountDownLatch是没什么区别的,正如前文所说的CyclicBarrier的功能更加的复杂且强大。给大家看一个《实战Java高并发程序设计》一书上的一个例子。

比如:司令下达命令,要求10个士兵去一起完成一项任务。这时,就会要求10个士兵先集合报道,接着,一起雄赳赳气昂昂地去执行任务。当10个士兵都执行完了任务,那么司机就可以对外宣称,任务完成。相比CountDownLatch,CyclicBarrier可以接受一个参数作为BarrierAction。所谓的BarrierAction就是当计数器一次计数完成后,系统会执行的动作。如下构造函数,其中,parties表示技术总数,也就是参与的线程总数。

public CyclicBarrier(int parties, Runnable barrierAction)

下面示例演示了上述任务场景

public class CyclicBarrierDemo {
  public static class Soldier implements Runnable {
    private String soldier;
    private final CyclicBarrier cyclicBarrier;
    public Soldier(CyclicBarrier cyclicBarrier, String soldier) {
      this.soldier = soldier;
      this.cyclicBarrier = cyclicBarrier;
    }
    @Override
    public void run() {
      try {
        cyclicBarrier.await();
        doWork();
        cyclicBarrier.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;
    }
    @Override
    public void run() {
      if (flag) {
        System.out.println("司令:[士兵" + N + "个,任务完成!");
      } else {
        System.out.println("司令:[士兵" + N + "个,集合完毕!");
        flag = true;
      }
    }
  }
  public static void main(String args[]) {
    final int N = 10;
    Thread[] allSoldier = new Thread[N];
    boolean flag = false;
    CyclicBarrier cyclicBarrier = 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(cyclicBarrier, "士兵" + i));
      allSoldier[i].start();
    }
  }
}

执行结果如下:

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

总结

以上就是本文关于Java并发实例之CyclicBarrier的使用的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:

Javaweb应用使用限流处理大量的并发请求详解

java并发学习之BlockingQueue实现生产者消费者详解

Java系统的高并发解决方法详解

如有不足之处,欢迎留言指出。期待您的宝贵意见!

相关文章

  • MyBatis按时间排序方式

    MyBatis按时间排序方式

    这篇文章主要介绍了MyBatis按时间排序方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • Mybatis-Plus同时使用逻辑删除和唯一索引的问题及解决办法(报数据重复Duplicate entry的问题)

    Mybatis-Plus同时使用逻辑删除和唯一索引的问题及解决办法(报数据重复Duplicate entry的

    在开发中,我们经常会有逻辑删除和唯一索引同时使用的情况,但当使用mybatis plus时,如果同时使用逻辑删除和唯一索引,会报数据重复Duplicate entry的问题,如何解决这个问题呢,小编给大家分享Mybatis-Plus同时使用逻辑删除和唯一索引的问题及解决办法,一起看看吧
    2023-11-11
  • 简单易用的Spring Boot邮件发送demo

    简单易用的Spring Boot邮件发送demo

    本文将介绍如何使用Spring Boot发送邮件,我们将演示如何配置SMTP邮件服务器,创建一个邮件模板,以及如何使用JavaMailSender发送邮件,我们还将介绍如何测试我们的邮件发送代码
    2023-12-12
  • Java实现拓扑排序算法的示例代码

    Java实现拓扑排序算法的示例代码

    在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。本文将为大家讲讲拓扑排序算法的原理及实现,需要的可以参考一下
    2022-07-07
  • java外部类与内部类的关系详解

    java外部类与内部类的关系详解

    本文详细讲解了java外部类与内部类的关系,用代码演示了外部类调用内部类的方法。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • SpringBoot监控模块Actuator的用法详解

    SpringBoot监控模块Actuator的用法详解

    Spring Boot Actuator 是 Spring Boot 自带的一个功能模块,提供了一组已经开箱即用的生产环境下常用的特性和服务,比如应用程序的健康检查、信息暴露、度量收集、日志记录等,本文将给大家详细SpringBoot监控模块Actuator的用法
    2023-06-06
  • Java通过XPath获取XML文件中符合特定条件的节点

    Java通过XPath获取XML文件中符合特定条件的节点

    今天小编就为大家分享一篇关于Java通过XPath获取XML文件中符合特定条件的节点,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • Java使用线程池批量处理数据操作具体流程

    Java使用线程池批量处理数据操作具体流程

    这篇文章主要给大家介绍了关于Java使用线程池批量处理数据操作的相关资料,Java多线程编程中线程池是一个非常重要的概念,线程池可以提高线程的复用率和任务调度的效率,尤其是当需要查询大批量数据时,需要的朋友可以参考下
    2023-06-06
  • Java实现人脸识别登录、注册等功能(最新完整版)

    Java实现人脸识别登录、注册等功能(最新完整版)

    这段时间由于学校实行静态化管理,寝室门和校门都是用了人脸识别的装置,本系列项目从设计到实现源码全部开源免费学习使用,对Java实现人脸识别登录、注册功能感兴趣的朋友一起看看吧
    2022-05-05
  • java 关键字super详解及用法

    java 关键字super详解及用法

    这篇文章主要介绍了java 关键字super详解及用法的相关资料,需要的朋友可以参考下
    2017-03-03

最新评论