四十、并发工具之CyclicBarrier

40 阅读4分钟

CyclicBarrier

CyclicBarrier概念

CyclicBarrier有一个屏障的概念,线程到达屏障后,就会挂起。当多个线程到达屏障,这个屏障就会打开,让这些线程继续执行。而且CyclicBarrier可以重复使用。

CyclicBarrier是基于ReentrantLock来实现并发安全的。

CyclicBarrier用法

背景栏:

假设Tom、Jack、Rose三个人去旅游,导游等三个人到齐后分发护照,然后再出发。

板书栏:

public class Test {

    public static void main(String[] args) {

        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("人到齐了,分发护照");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("一起出发");
        });

        new Thread(() -> {
            System.out.println("Tom来了");
            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("Tom拿到护照");
        }).start();

        new Thread(() -> {
            System.out.println("Jack来了");
            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("Jack拿到护照");
        }).start();

        new Thread(() -> {
            System.out.println("Rose来了");
            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("Rose拿到护照");
        }).start();
    }
}

要点栏:

要点1:

在构建CyclicBarrier可以指定一个任务,这个任务会在barrier归0时执行,然后再去唤醒挂起的线程,并行执行后续的任务。

要点2:

互相等待的线程,可以指定等待时间,并且在等待过程中,如果有一个线程中断,其他线程也会被唤醒,并且CyclicBarrier无法再继续使用。除非调用reset方法重置CyclicBarrier。

要点3:

如果CyclicBarrier的屏障数值到达0后,会默认重置屏障数值,CyclicBarrier在没有线程中断的情况下是可以重复使用的。

CyclicBarrier源码

基本属性

板书栏:

public class CyclicBarrier {

	// 用来记录是否有线程中断
    private static class Generation {
        boolean broken = false;
    }

	// 互斥锁
    private final ReentrantLock lock = new ReentrantLock();
 
    private final Condition trip = lock.newCondition();
  
	// 用来记录屏障数量,不会对其进行加减操作
    private final int parties;
 
	// CyclicBarrier的有参构造传入的任务
    private final Runnable barrierCommand;

	// 初始化generation
    private Generation generation = new Generation();
   
	// 用来记录屏障数量,会对其进行加减操作 
    private int count;
}

基本方法

板书栏:

public int await() throws InterruptedException, BrokenBarrierException {
	try {
		// 有参和无参的await方法,都是调用的dowait方法
		return dowait(false, 0L);
	} catch (TimeoutException toe) {
		throw new Error(toe); 
	}
}
private int dowait(boolean timed, long nanos)
	throws 
	// 当前线程中断抛的异常
	InterruptedException,
	// 其他线程中断抛的异常
	BrokenBarrierException,
	// 等待超时抛的异常
	TimeoutException {
	final ReentrantLock lock = this.lock;
	// 加锁
	lock.lock();
	try {
		// 获取generation
		final Generation g = generation;
		// 判断其他线程是否中断
		if (g.broken)
			// 其他线程中断,抛出异常
			throw new BrokenBarrierException();
		// 判断当前线程是否中断
		if (Thread.interrupted()) {
			// generation中的broken设置为true
			// 重置count
			// 唤醒其他线程
			breakBarrier();
			// 当前线程中断抛出异常
			throw new InterruptedException();
		}

		// count减1赋值给index
		int index = --count;
		// 判断count减1后是否为0
		if (index == 0) {  
			// 说明当前线程是最后一个到达屏障的
			boolean ranAction = false;
			try {
				// 拿到barrier任务
				final Runnable command = barrierCommand;
				if (command != null)
					// barrier任务执行
					command.run();
				// barrier任务正常执行完成
				// ranAction设置为true
				ranAction = true;
				// 重置generation
				// 重置count
				// 唤醒其他线程
				nextGeneration();
				返回屏障数
				return 0;
			} finally {
				// 判断barrier是否正常执行完成
				if (!ranAction)
					// 进入这里说明barrier任务没有正常执行完成,可能抛了异常
					// generation中的broken设置为true
					// 重置count
					// 唤醒其他线程
					breakBarrier();
			}
		}

		// 到这里说明当前线程不是最后一个到达屏障的
		for (;;) {
			try {
				// 判断是否有等待时间
				if (!timed)
					trip.await();
				else if (nanos > 0L)
					nanos = trip.awaitNanos(nanos);
			} catch (InterruptedException ie) {
				// 先判断generation是否被重置(可能有线程执行了reset方法导致被重置)
				// 如果没有被重置,判断其他线程是否有中断
				if (g == generation && ! g.broken) {
					// generation没有被重置,并且其他线程没有中断
					// 说明当前线程中断
					// generation中的broken设置为true
					// 重置count
					// 唤醒其他线程
					breakBarrier();
					// 抛出当前线程中断异常
					throw ie;
				} else {
					// 到这里说明有线程调用了reset方法
					// 当前线程设置中断
					Thread.currentThread().interrupt();
				}
			}

			// 判断其他线程是否有中断
			if (g.broken)
				// 抛出其他线程中断异常
				throw new BrokenBarrierException();

			// 判断generation是否被重置
			if (g != generation)
				// 到这里有两种情况
				// 第一种,有线程调用的reset方法
				// 第二种,最后一个线程到达屏障,唤醒了其他线程
				return index;
			// 判断等待时间是否已过
			if (timed && nanos <= 0L) {
				breakBarrier();
				throw new TimeoutException();
			}
		}
	} finally {
		lock.unlock();
	}
}
private void breakBarrier() {
	generation.broken = true;
	count = parties;
	trip.signalAll();
}
private void nextGeneration() {
	// signal completion of last generation
	trip.signalAll();
	// set up next generation
	count = parties;
	generation = new Generation();
}

要点栏:

要点1:

当所有线程到达屏障,重置generation;而当有线程中断,会将generation中的broken设置为true。根据这个来判断await方法是正常返回,还是要抛出异常。

要点2:

如果是当前线程中断,抛出的是InterruptedException异常;如果是其他线程中断,抛出的是BrokenBarrierException异常;如果是等待超时,抛出的是TimeoutException异常。

要点3:

如果在所有线程还未到达屏障时,有线程调用了reset方法,会重置generation和count,并且唤醒已经挂起的线程。被唤醒的线程会正常退出await方法,继续执行任务。