CyclicBarrier是栅栏,效果就是让多个线程都执行到某个指定的点之后,再一起继续执行。与CountDownLatch有点类似,最大的区别是CyclicBarrier可以循环使用。
这里举例两个场景,一个是斗地主,需要三个玩家都入场之后才可以开始。 另一个是赛马,多匹马一起跑,最先到达终点的算赢。这里用线程分别表示玩家和马。
三人斗地主代码如下:
(需要三个玩家入场之后才能发牌)
public class Player extends Thread {
private static int count = 1;
private final int id = count++;
private CountDownLatch latch;
public Player(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
System.out.println("【玩家" + id + "】已入场");
latch.countDown();//计数器减1
}
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3); //初始化计数器
System.out.println("牌局开始, 等待玩家入场...");
new Player(latch).start();//启动玩家线程
new Player(latch).start();////启动玩家线程
new Player(latch).start();////启动玩家线程
latch.await();//阻塞等待计数器为0
System.out.println("玩家已到齐, 开始发牌...");
}
}
结果如下:
牌局开始, 等待玩家入场…
【玩家2】已入场
【玩家1】已入场
【玩家3】已入场
玩家已到齐, 开始发牌…
如果不使用CountDownLatch,我们直接注释掉//latch.await(); 看结果
牌局开始, 等待玩家入场…
【玩家1】已入场
【玩家2】已入场
玩家已到齐, 开始发牌…
【玩家3】已入场
三个玩家没凑齐就发牌了。
原理:CountDownLatch
构造函数中创建了Sync这个内部类的对象
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
内部类Sync继承了AbstractQueuedSynchronizer 简称AQS,AQS可以理解成是java里面用java代码实现的Synchronize,而不是由jvm去实现的。在AQS里面维护了一个state ,以及一个同步队列和多个条件队列
想要锁但是没获取到锁的线程,都在同步队列里面等着,获取到锁但是没有满足执行条件的,都在条件队列里面等着。
调用latch.await();的线程,被加入到同步队列里面等着,直到计数器为0释放锁。
它的内部提供了一个计数器,在构造闭锁时必须指定计数器的初始值,且计数器的初始值必须大于0。另外它还提供了一个countDown方法来操作计数器的值,每调用一次countDown方法计数器都会减1,直到计数器的值减为0时就代表条件已成熟,所有因调用await方法而阻塞的线程都会被唤醒。这就是CountDownLatch的内部机制
CountDownLatch只有一个带参构造器,必须传入一个大于0的值作为计数器初始值,否则会报错。可以看到在构造方法中只是去new了一个Sync对象并赋值给成员变量sync。和其他同步工具类一样,CountDownLatch的实现依赖于AQS,它是AQS共享模式下的一个应用。CountDownLatch实现了一个内部类Sync并用它去继承AQS,这样就能使用AQS提供的大部分方法了。下面我们就来看一下Sync内部类的代码。
//同步器
private static final class Sync extends AbstractQueuedSynchronizer {
//构造器
Sync(int count) {
setState(count);
}
//获取当前同步状态
int getCount() {
return getState();
}
//尝试获取锁
//返回负数:表示当前线程获取失败
//返回零值:表示当前线程获取成功, 但是后继线程不能再获取了
//返回正数:表示当前线程获取成功, 并且后继线程同样可以获取成功
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//尝试释放锁
protected boolean tryReleaseShared(int releases) {
for (;;) {
//获取同步状态
int c = getState();
//如果同步状态为0, 则不能再释放了
if (c == 0) {
return false;
}
//否则的话就将同步状态减1
int nextc = c-1;
//使用CAS方式更新同步状态
if (compareAndSetState(c, nextc)) {
return nextc == 0;
}
}
}
}
赛马游戏的代码如下:
马
`
public class Horse implements Runnable {
private static int counter = 0;
private final int id = counter++;
private int strides = 0;
private static Random rand = new Random(47);
private static CyclicBarrier barrier;
public Horse(CyclicBarrier CyclicBarrier) {
this.barrier = CyclicBarrier;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (this) {
//赛马每次随机跑几步
strides += rand.nextInt(3);
}
barrier.await();//线程在此进行等待
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String tracks() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < getStrides(); i++) {
s.append("*");
}
s.append(id);
return s.toString();
}
public synchronized int getStrides() {
return strides;
}
public String toString() {
return "Horse " + id + " ";
}
}
赛马过程
public class HorseRace implements Runnable {
private static final int FINISH_LINE = 75;
private static List<Horse> horses = new ArrayList<Horse>();
private static ExecutorService exec = Executors.newCachedThreadPool();
@Override
public void run() {
StringBuilder s = new StringBuilder();
//打印赛道边界
for(int i = 0; i < FINISH_LINE; i++) {
s.append("=");
}
System.out.println(s);
//打印赛马轨迹
for(Horse horse : horses) {
System.out.println(horse.tracks());
}
//判断是否结束
for(Horse horse : horses) {
if(horse.getStrides() >= FINISH_LINE) {
System.out.println(horse + "won!");
exec.shutdownNow();
return;
}
}
//休息指定时间再到下一轮
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch(InterruptedException e) {
System.out.println("barrier-action sleep interrupted");
}
}
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(7, new HorseRace()); //需要到达的线程数,以及线程到达之后需要执行的线程
for(int i = 0; i < 7; i++) {
Horse horse = new Horse(barrier);
horses.add(horse);
exec.execute(horse);
}
}
}
执行结果
如下
===============
**0
**1
*2
**3
*4
**5
*6
。。。。。省略n个
===============
***********0
************1
********2
***********3
**********4
***************5
************6
Horse 5 won!
执行barrier.await();的线程进行等待,直到所有计数器到达设置的值,然后继续执行下一轮。

本文对比分析了CyclicBarrier与CountDownLatch在多线程并发控制中的作用及应用场景,通过斗地主和赛马游戏的示例,详细解释了两者的工作原理和使用方法。
828

被折叠的 条评论
为什么被折叠?



