从赛马游戏看CyclicBarrier,从斗地主看CountDownLatch

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

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` 都是 Java 并发包 (`java.util.concurrent`) 中用于线程间同步的工具类,它们帮助协调多个线程之间的操作。尽管两者的目的都是为了控制一组线程的行为,但在使用场景上有所不同。 ### CyclicBarrier **特点:** - **循环屏障 (Cyclic Barrier)** 允许多个线程互相等待到达一个公共屏障点后再继续执行。 - 支持设置一个当所有线程都达到屏障时触发的动作(通过构造函数指定)。此动作将由最后一个进入屏障的线程执行。 - 可以复用多次(即一旦所有的参与者完成了一轮等待,则可以开始下一轮),这也是它名字中“Cyclic”(循环) 的含义所在。 **典型应用场景:** 适合需要等若干协作任务全部准备就绪再一起启动的情况;例如模拟赛马比赛,在所有选手准备好之前不会开跑。 --- ### CountDownLatch **特点:** - 计数下降锁存器是一个同步辅助工具,允许一个或更多线程一直等待直到其他一些线程已完成一系列的操作。 - 初始化时给定计数值,并随着 countDown() 调用减少该值。await() 方法会阻塞当前线程直至计数归零。 - 一次性的,不可重置计数器——如果想要类似功能但支持重启的话应考虑使用 `CyclicBarrier` 或者创建新的实例。 **典型应用场景:** 适用于某个事件依赖于一系列其它独立子任务的成功完成的情形;比如主服务端程序要等到各个客户端连接建立完毕才能继续往下处理业务逻辑。 --- 总结来说,选择哪种取决于具体的并发需求: - 如果你需要反复利用相同的同步机制(`CyclicBarrier`); - 若只是单纯的一次性障碍则选用(`CountDownLatch`)即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值