Java_12_CountDownLatch闭锁_CyclicBarries栅栏

本文介绍了Java中两种重要的同步工具——CountDownLatch和CyclicBarrier,包括它们的作用、使用案例及底层实现原理。CountDownLatch是一个一次性闭锁,用于等待一组线程完成,而CyclicBarrier是可重用的栅栏,允许线程在固定点汇集。两者的区别在于重置能力及应用场景。
摘要由CSDN通过智能技术生成

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 提交测试结果
// 开始汇总测试结果,提交测试报告
底层原理
/**
 * 在上面我们看到,CountDownLatch主要使用countDown方法进行减1操作,使用await方法进行等到操作。
 * 我们进入到源码中看看。本源码基于jdk1.8。特在此说明。
 * --------------------------------------------------------------------------------------
 * Decrements the count of the latch, releasing all waiting threads if
 * the count reaches zero.
 * 减少锁的计数,如果计数达到零,则释放所有等待的线程。
 * <p>If the current count is greater than zero then it is decremented.
 * If the new count is zero then all waiting threads are re-enabled for
 * thread scheduling purposes.
 * 如果当前计数大于零,则递减。如果新计数为零,则重新启用所有等待线程以用于线程调度目的。
 * <p>If the current count equals zero then nothing happens.
 * 如果当前计数为零,则什么也不会发生。
 */
public void countDown() {
    sync.releaseShared(1);
}
/**
 * 在这里我们发现Sync继承了AbstractQueuedSynchronizer(AQS)。
 * AQS的其中一个作用就是维护线程状态和获取释放锁。在这里也就是说CountDownLatch使用AQS机制维护锁状态。
 * 而releaseShared(1)方法就是释放了一个共享锁。
 * ------------------------------------------------------------------------------------------
 * Synchronization control For CountDownLatch.
 * Uses AQS state to represent count.
 * CountDownLatch 的同步控制。使用 AQS 状态来表示计数。
 */
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;
        }
    }
}
/**
 * Causes the current thread to wait until the latch has counted down to
 * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
 * 导致当前线程等待,直到锁存器倒计时为零,除非线程被中断。
 * 
 * <p>If the current count is zero then this method returns immediately.
 * 如果当前计数为零,则此方法立即返回。
 * 
 * <p>If the current count is greater than zero then the current
 * thread becomes disabled for thread scheduling purposes and lies
 * dormant until one of two things happen:
 * 如果当前计数大于零,则当前线程出于线程调度目的而被禁用并处于休眠状态,直到发生以下两种情况之一:
 * <ul>
 * <li>The count reaches zero due to invocations of the
 * {@link #countDown} method; or
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread.
 * </ul>
 * 由于调用countDown方法,计数达到零;或者 其他某个线程中断了当前线程。
 * <p>If the current thread:
 * <ul>
 * <li>has its interrupted status set on entry to this method; or
 * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
 * </ul>
 * then {@link InterruptedException} is thrown and the current thread's
 * interrupted status is cleared.
 * 或者 等待时被打断, 然后抛出InterruptedException并清除当前线程的中断状态。
 * @throws InterruptedException if the current thread is interrupted
 *         while waiting
 * InterruptedException – 如果当前线程在等待时被中
 */
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 进入游戏......
底层原理
/** The lock for guarding barrier entry */
/** 守卫栅栏入口的锁 */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
/** 参与阻塞的线程数量 */
private final Condition trip = lock.newCondition();
/** The number of parties */
/** 参与阻塞的线程数量 */
private final int parties;
/** The command to run when tripped */
/** 栅栏被推倒时执行的任务 */
private final Runnable barrierCommand;
/** The current generation */
/** 当前这一代 */
private Generation generation = new Generation();

/**
 * Number of parties still waiting. Counts down from parties to 0
 * on each generation.  It is reset to parties on each new
 * generation or when broken.
 * 仍在等待的线程数量,每一代从各方倒数到 0,它会在新一代或损坏时重置。
 */
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
    }
}


/* dowait(boolean, long)方法的主要逻辑处理比较简单,如果该线程不是最后一个调用await方法的线程,则它会一直处于等待状态,除非发生以下情况:
* 1.最后一个线程到达,即index == 0
* 2.某个参与线程等待超时
* 3.某个参与线程被中断
* 4.调用了CyclicBarrier的reset()方法。该方法会将屏障重置为初始状态
*/

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()方法用来了解阻塞的线程是否被中断。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值