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();
}
}
闭锁与栅栏
二者区别
- 使用次数:CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景。
- 应用场景:CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。
- 提供方法:CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断。