CyclicBarrier是JDK 1.5 concurrent包出现的一个用于解决多条线程阻塞,当达到一定条件时一起放行的一个类。我们先来看这样一个简单的需求。

  现在我有一个写入数据的类,继承Runable接口:

public class WriteDateThread implements Runnable {

    @Override
public void run() { System.out.println(Thread.currentThread().getName() + "开始写入数据..."); System.out.println(Thread.currentThread().getName() + "写入数据完毕!");
}
}

  代码很简单,只有两个输出语句,一个是开始写入数据,然后打印写入完毕。

  然后创建这样一个Main函数的类:

public class CyclicBarrierMain {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
WriteDateThread thread = new WriteDateThread();
Thread t = new Thread(thread);
t.start();
}
}
}

  同样,也是很简单的代码,我现在通过main函数来启动这样5个WriteDateThread线程,想必大家应该清楚线程之间的执行顺序在于谁抢到了CPU执行权,于是执行结果如下:

  

  我们可看到,每条线程的开始执行和执行完毕和其他线程都是交错的,这也符合了多线程的执行规律。我们来想象一个实际中的需求,现在有5个玩家同时进入一场游戏对决当中,系统需要读取玩家的数据,当读取完成后,允许玩家进入游戏中。按理来说,当系统读取完某玩家的数据后,该玩家就拥有入场权限了,如果我们不加任何干预的话,会发生什么情况呢?正如上图中执行结果那样,系统读取0,2号玩家的数据,然后2号玩家读取完毕,于是2号玩家先进场,然后再读取4,1号玩家的数据...以此类推。我想大家肯定玩过LOL或者农药这类的多人竞技游戏,实际情况是这样的吗?不是!实际情况是,系统加载完某玩家的数据后,会让该玩家等待其他玩家的数据加载,当所有玩家的数据都加载完毕后,大家同时进入游戏(这里不考虑掉线等意外情况)。

  好了,有了这样一个实际案例,我说出现在的需求大家就很容易理解了,现在的需求是要求所有线程在执行完写入数据后,不能在向下执行,而是等待所有其他线程执行完写入数据,然后大家再一起执行“写入数据完毕...”,我们当然可以用java.util.concurrent包下CountDownLatch计数器来自行控制,但是这里我希望用更智能化一些的CyclicBarrier(循环栅栏)来完成这个需求:

  代码如下:

 public class WriteDateThread implements Runnable {

     private CyclicBarrier barrier;

     public WriteDateThread(CyclicBarrier barrier) {
this.barrier = barrier;
} @Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始写入数据...");
try {
barrier.await(); //通过调用await方法在此处设置栅栏,使得线程执行到此进入阻塞状态等待其他线程执行完成。
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "写入数据完毕!");
}
}
 public class CyclicBarrierMain {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(5);
for (int i = 0; i < 5; i++) {
WriteDateThread thread = new WriteDateThread(barrier);
Thread t = new Thread(thread);
t.start();
}
}
}

  我们通过调用CyclicBarrier的await方法,使线程执行到此的时候,进入到阻塞状态,并且等待其他线程都执行到此后,再统一放行,让它们同时向下继续执行(放行后具体谁先执行,还是要看谁先抢到CPU执行权)

  运行结果:

  

  如此,便得到了我们需求中想要的结果了。

  CyclicBarrier的用法到此就介绍完毕,下面我着重介绍一下,CyclicBarrier最常用的一个构造函数,也是我第一次使用时非常令我困惑的一个点,就是我上面代码CyclicBarrierMain中第3行CyclicBarrier barrier = new CyclicBarrier(int parties);这个构造器。

  我们现在有5条线程,因此我传入5这毫无疑问,但是这个5究竟是什么意思呢?如果传入的值大于5或者小于5又会是什么结果呢?大家不妨可以自行试验一下,只要我们传入的不是5,程序都会一直处于阻塞状态,停不下来。

  我翻看过别人别这个值的解释,比较抽象,原文如下:

  参数parties指定线程数量,当指定的线程值都到达栅栏点时,栅栏打开,线程恢复。需要注意的是,当指定的线程数量大于启动的线程数量,比如修改上例中的代码,只启动4个线程,那么所有的线程将执行到await处然后一直处于等待状态。第二种情况是指定的线程数量小于启动的线程,上例代码,启动6个线程,那么当第5个线程到达栅栏点时,那么这5个线程就会恢复继续执行,而第6个线程将一直处于阻塞状态。

  我将通过一个小故事来说明这个问题。

  故事是这样的,有这样一场骑马比赛,有5名骑手,他们各自拥有自己的赛道,并且每条赛道上在开赛之前就已经预设好了一个栅栏。另外还有一名栅栏管理员和一名指挥官,指挥官只负责向管理员发号施令,他只会告诉管理员一个数字n。这个数字的意思是,管理员要打开的栅栏的数量,前提是他必须看到有n个骑手都到达栅栏前,才会将这n个骑手面前的栅栏打开。于是管理会记着这个数字n在栅栏旁守着,当有1名、2名...骑手到达栅栏时,管理员不予理会,直到有第n名骑手到达栅栏处时,管理员统一打开栅栏放行,骑手们得以同时继续骑行,而管理员则完成了任务,进屋休息去了。

  如果指挥官告诉管理员的数字是5,那么一切和平的进行,不会发生任何问题。但是如果指挥官,告诉管理员的数字不是5,比如说4或者6。这样问题就产生了,对于这两种情况,我们分别来分析一下:

  • 指挥官给出的数字大于骑手数量,这里我们假设是6

  我前边说过,每条赛道上在开赛之前都预设好了栅栏,那么此时管理从指挥官那里得到的数字是6,因此管理员要执行的任务就是看着栅栏,直到第1名骑手到达,第2名骑手到达,第3名...直到管理员看见第6名骑手到达时才进行统一放行,但是问题是,总共的参赛选手只有5名啊!这下好了,管理员是个死心眼,他不看见第6名骑手他是绝对不会进行放行的,那么大家可以想象一下,5名骑手被卡在栅栏前不能继续后面的比赛,而管理员在那里痴痴的等着第6名骑手的到来,就这样一直到天荒地老...

  • 指挥官给出的数字小于骑手数量,这里我们假设是4

  还像刚才一样,管理拿到数字4后便在栅栏旁等着,直到看见骑手的相继到来,第1名,第2名,第3名,第4名!管理员看见第4名骑手到来后,立即打开他们各自赛道前的栅栏,于是这4名骑手继续后面的比赛,管理员也完成了自己的任务,进屋睡觉去了...等等!还有第5名骑手呢!第5名骑手到达栅栏前惊奇的发现,其他赛道都被放行了,但是他的赛道上还有栅栏,他不得不等下等着管理员放行,但是郁闷的是,他并不知道管理员再也不会回来了,于是他就这样孤独的一直等待着...

  

  好了,故事讲完了,现在我们通过这个故事映射到代码当中,骑手其实就是我们开的线程,他们各自赛道上赛前设置的栅栏,其实就是我们在程序执行前调用的CyclicBarrier的await方法,程序执行到这个栅栏处会被阻塞住。指挥官其实就是我们自己(写程序的人),管理员就是CyclicBarrier内部实现唤醒线程的解决方案,我们(指挥官)给管理员发号的施令(一个数字)就是我们通过CyclicBarrier(int parties)传入的数值。

  如果我们传入的是6,因为我们只有5条线程,第6条线程永远不会到来,因此管理员也就永远不会放行栅栏,所有线程将会阻塞在栅栏出等待那不存在的第6条线程,我们更改代码中传入的数值为6,得到的运行结果:

  

  可以看到,5条线程全部阻塞到了调用await方法的地方,并且程序一直处于阻塞状态。

  

  如果我们传入的是4,管理员在看到第4条线程的到来时,就会放行他们4个面前的栅栏,于是这4条线程继续执行,此时管理员完成任务回家睡觉了,第5条线程到来时,会一直卡在栅栏前。我们更改代码中传入的数值为4,得到的运行结果:

  

  

  通过上述的通俗的讲解,相信大家不会在觉得CyclicBarrier的(int parties)构造方法那么晦涩难懂了,由于本人所知有限,故难保证文中有什么错误之处,欢迎大牛不惜吝啬下方留言指正。

戏说java多线程之CyclicBarrier(循环栅栏)的CyclicBarrier(int parties)构造方法的更多相关文章

  1. java并发之(4):Semaphore信号量、CounDownLatch计数锁存器和CyclicBarrier循环栅栏

    简介 java.util.concurrent包是Java 5的一个重大改进,java.util.concurrent包提供了多种线程间同步和通信的机制,比如Executors, Queues, Ti ...

  2. Java多线程之ConcurrentSkipListMap深入分析(转)

    Java多线程之ConcurrentSkipListMap深入分析   一.前言 concurrentHashMap与ConcurrentSkipListMap性能测试 在4线程1.6万数据的条件下, ...

  3. JAVA多线程之wait/notify

    本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法. ①wait() 与 notify/notifyAll 方法必须在同步代码块中使用 ②wait ...

  4. JAVA多线程之volatile 与 synchronized 的比较

    一,volatile关键字的可见性 要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下: 从图中可以看出: ①每个线程都有一个自己的本地内存空间--线程栈空 ...

  5. Java多线程之Runnable与Thread

    Java多线程之Thread与Runnable 一.Thread VS Runnable 在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口:Thread类和 ...

  6. java多线程之yield,join,wait,sleep的区别

    Java多线程之yield,join,wait,sleep的区别 Java多线程中,经常会遇到yield,join,wait和sleep方法.容易混淆他们的功能及作用.自己仔细研究了下,他们主要的区别 ...

  7. JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止

    JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止 背景 当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境 ...

  8. java多线程之wait和notify协作,生产者和消费者

    这篇直接贴代码了 package cn.javaBase.study_thread1; class Source { public static int num = 0; //假设这是馒头的数量 } ...

  9. 同步机制之--java CyclicBarrier 循环栅栏

    CyclicBarrier介绍一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待 ...

随机推荐

  1. PhpStorm的破解 汉化

    以前一直习惯使用sublime,最近发现phpstorm比submit稍微更强大些,其很多插件都是直接可以使用,不需要另外去拓展了 其中的破解.汉化步骤就需要借助一些资源 (1)破解 安装完毕后,直接 ...

  2. CentOS 7 安装Java 1.8

    携程的Apollo配置中心服务端[https://github.com/ctripcorp/apollo/wiki]推荐的Java版本是:1.8+, 本文介绍如何在CentOS上安装java 1.8. ...

  3. CentOS6.8配置GO语言开发环境

    Go语言是谷歌2009发布的第二款开源编程语言,Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全.支持并行进程. 鉴于原来越多的开源项 ...

  4. iOS 开发 atomic 与 nonatomic 区别

    atomic :  变量默认是有该有属性的,这个属性是为了保证在多线程的情况下,编译器会自动生成一些互斥加锁的代码,避免该变量的读写不同步的问题. nonatomic  : 如果该对象无需考虑多线程的 ...

  5. JavaScript数据迭代方法差别

    js有很多总接待方法,ES6之后又新增了几个: 这里主要讨论数组迭代遍历的方法所以不会细讲for...in... ES5.ES6数组迭代方法有: forEach map filter some eve ...

  6. SDP(8):文本式数据库-MongoDB-Scala基本操作

    MongoDB是一种文本式数据库.与传统的关系式数据库最大不同是MongoDB没有标准的格式要求,即没有schema,合适高效处理当今由互联网+商业产生的多元多态数据.MongoDB也是一种分布式数据 ...

  7. JPA数据懒加载LAZY和实时加载EAGER(二)

    懒加载LAZY和实时加载EAGER的概念,在各种开发语言中都有广泛应用.其目的是实现关联数据的选择性加载,懒加载是在属性被引用时,才生成查询语句,抽取相关联数据.而实时加载则是执行完主查询后,不管是否 ...

  8. Java中空串和null串的区别

    对于空串来说这是一个对象他被""这个对象给实例化了只是他的长度为0字符的内容为空. 而String变量中还可以存储一个特殊的值,这个是null,这个表示没有和其他的对象与这个变量相 ...

  9. C语言_了解一下C语言中的四种存储类别

    C语言是一门通用计算机编程语言,应用广泛.C语言的设计目标是提供一种能以简易的方式编译.处理低级存储器.产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言. C语言中的四种存储类别:auto ...

  10. CentOs下Mongodb的下载与安装

    1.下载MongoDB(64位) http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.9.tgz tar zxvf mongodb-lin ...