万知网

Java(java之父)

时间:2023-06-28

Java

文章目录
      • CountDownLatch闭锁
        • 相关介绍
        • 使用案例
        • 底层原理
      • CyclicBarrier栅栏
        • 相关介绍
        • 使用案例
        • 底层原理
      • 闭锁与栅栏
        • 二者区别
CountDownLatch闭锁 相关介绍

作用:就是一个或者一组线程在开始执行操作之前,必须要等到其他线程执行完才可以执行。我们举一个例子来说明,在考试的时候,老师必须要等到所有人交了试卷才可以走。此时老师就相当于等待线程,而学生就好比是执行的线程。

使用案例
CountDownLatch countDownLatch = new CountDownLatch(2);System.out.println("开始测试,测试人员就绪");new Thread(() -> {    try {        Thread.sleep(3000);        System.out.println("测试人员 1 提交测试结果");    } catch (InterruptedException e) {        e.printStackTrace();    }finally {        countDownLatch.countDown();    }}).start();new Thread(() -> {    try {        Thread.sleep(7000);        System.out.println("测试人员 2 提交测试结果");    } catch (InterruptedException e) {        e.printStackTrace();    }finally {        countDownLatch.countDown();    }}).start();new Thread(() -> {    try {        countDownLatch.await();        System.out.println("开始汇总测试结果,提交测试报告");    } catch (InterruptedException e) {        e.printStackTrace();    }}).start();// 输出结果// 开始测试,测试人员就绪// 测试人员 1 提交测试结果// 测试人员 2 提交测试结果// 开始汇总测试结果,提交测试报告
底层原理
public void countDown() {    sync.releaseShared(1);}
private static final class Sync extends AbstractQueuedSynchronizer {    private static final long serialVersionUID = 4982264981922014374L;    Sync(int count) {        setState(count);    }    int getCount() {        return getState();    }    protected int tryAcquireShared(int acquires) {        return (getState() == 0) ? 1 : -1;    }    protected boolean tryReleaseShared(int releases) {        // Decrement count; signal when transition to zero        for (;;) {            int c = getState();            if (c == 0)                return false;            int nextc = c-1;            if (compareAndSetState(c, nextc))                // 如果当前状态值等于预期值 c,则以原子方式将同步状态设置为给定的更新值 nextc。                return nextc == 0;        }    }}
public void await() throws InterruptedException {    sync.acquireSharedInterruptibly(1);}
CyclicBarrier栅栏 相关介绍
  • 栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
  • CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
使用案例
public class CyclicBarriesTest {    public static void main(String[] args) {        int playerCount = 3;        CyclicBarrier cyclicBarrier = new CyclicBarrier(playerCount);        for (int i = 0; i < playerCount; i++) {            Player player = new Player(cyclicBarrier);            player.setName(String.valueOf(i));            player.start();        }    }    static class Player extends Thread {        CyclicBarrier cyclicBarrier;        public Player(CyclicBarrier cyclicBarrier) {            this.cyclicBarrier = cyclicBarrier;            //控制多个player使用同一个栅栏对象        }        @Override        public void run() {            try {                String name = Thread.currentThread().getName();                System.out.println("玩家 "+name+" 开始准备......");                int time = new Random().nextInt(10);                Thread.sleep(time * 1000);                System.out.println("玩家 "+name+" 准备就绪......");                cyclicBarrier.await();//阻塞等待其他线程到达栅栏                System.out.println("玩家 "+name+" 进入游戏......");            } catch (InterruptedException | BrokenBarrierException e) {                e.printStackTrace();            }        }    }}// 运行结果:// 玩家 0 开始准备......// 玩家 2 开始准备......// 玩家 1 开始准备......// 玩家 0 准备就绪......// 玩家 1 准备就绪......// 玩家 2 准备就绪......// 玩家 2 进入游戏......// 玩家 0 进入游戏......// 玩家 1 进入游戏......
底层原理
private final ReentrantLock lock = new ReentrantLock();private final Condition trip = lock.newCondition();private final int parties;private final Runnable barrierCommand;private Generation generation = new Generation();private int count;// CyclicBarrier内部使用了 ReentrantLock 和 Condition 两个类。它有两个构造函数:public CyclicBarrier(int parties) {    this(parties, null);} public CyclicBarrier(int parties, Runnable barrierAction) {    if (parties <= 0) throw new IllegalArgumentException();    this.parties = parties;    this.count = parties;    this.barrierCommand = barrierAction;}// 每个线程使用await()方法告诉CyclicBarrier我已经到达了栅栏处,然后当前线程被阻塞。public int await() throws InterruptedException, BrokenBarrierException {    try {        // 不超时等待        return dowait(false, 0L);    } catch (TimeoutException toe) {        throw new Error(toe); // cannot happen    }}private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {    // 获取独占锁    final ReentrantLock lock = this.lock;    lock.lock();    try {        // 当前代        final Generation g = generation;        // 如果这代损坏了,抛出异常        if (g.broken)            throw new BrokenBarrierException();         // 如果线程中断了,抛出异常        if (Thread.interrupted()) {            // 将损坏状态设置为true            // 并通知其他阻塞在此栅栏上的线程            breakBarrier();            throw new InterruptedException();        }         // 获取下标        int index = --count;        // 如果是 0,说明最后一个线程调用了该方法        if (index == 0) {  // tripped            boolean ranAction = false;            try {                final Runnable command = barrierCommand;                // 执行栅栏任务                if (command != null)                    command.run();                ranAction = true;                // 更新一代,将count重置,将generation重置                // 唤醒之前等待的线程                nextGeneration();                return 0;            } finally {                // 如果执行栅栏任务的时候失败了,就将损坏状态设置为true                if (!ranAction)                    breakBarrier();            }        }         // loop until tripped, broken, interrupted, or timed out        for (;;) {            try {                 // 如果没有时间限制,则直接等待,直到被唤醒                if (!timed)                    trip.await();                // 如果有时间限制,则等待指定时间                else if (nanos > 0L)                    nanos = trip.awaitNanos(nanos);            } catch (InterruptedException ie) {                // 当前代没有损坏                if (g == generation && ! g.broken) {                    // 让栅栏失效                    breakBarrier();                    throw ie;                } else {                    // 上面条件不满足,说明这个线程不是这代的                    // 就不会影响当前这代栅栏的执行,所以,就打个中断标记                    Thread.currentThread().interrupt();                }            }             // 当有任何一个线程中断了,就会调用breakBarrier方法            // 就会唤醒其他的线程,其他线程醒来后,也要抛出异常            if (g.broken)                throw new BrokenBarrierException();             // g != generation表示正常换代了,返回当前线程所在栅栏的下标            // 如果 g == generation,说明还没有换代,那为什么会醒了?            // 因为一个线程可以使用多个栅栏,当别的栅栏唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。            // 正是因为这个原因,才需要generation来保证正确。            if (g != generation)                return index;                        // 如果有时间限制,且时间小于等于0,销毁栅栏并抛出异常            if (timed && nanos <= 0L) {                breakBarrier();                throw new TimeoutException();            }        }    } finally {        // 释放独占锁        lock.unlock();    }}
闭锁与栅栏 二者区别
  1. 使用次数:CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景。
  2. 应用场景:CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。
  3. 提供方法:CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断。

热文推荐