(2.1.27.15)Java并发编程:Lock之CyclicBarrier公共屏障

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/fei20121106/article/details/83301771


CyclicBarrier 是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。

在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。

因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作很有用。

一、使用示例

比如统计文件行数,多个线程,最终都读完加一个总和。

public class TestBarrier {

    private static CyclicBarrier barrier;

    public static class CountThread extends Thread {

        @Override
        public void run() {
            // 业务逻辑
            System.out.println("线程" + Thread.currentThread().getName() + "读完了!");
            // 等待其它线程
            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            } 
        }

    }

    public static void main(String[] args) {

        barrier = new CyclicBarrier(10, new Runnable() {

            @Override
            public void run() {
                System.out.println("所有线程都读完了,可以合计总数了");
                // 业务逻辑
            }
        });

        for(int i=0; i<10; i++){
            new CountThread().start();
        }

    }

}

运行结果:

线程Thread-2读完了!
线程Thread-0读完了!
线程Thread-1读完了!
线程Thread-3读完了!
线程Thread-4读完了!
线程Thread-6读完了!
线程Thread-5读完了!
线程Thread-7读完了!
线程Thread-8读完了!
线程Thread-9读完了!
所有线程都读完了,可以合计总数了

二、源码分析

CyclicBarrier 是基于重入锁 ReentrantLock 实现相关逻辑的。所以要弄懂 CyclicBarrier 的源码,仅需有 ReentrantLock 相关的背景知识即可

2.1 构造方法及成员变量

CyclicBarrier 包含两个有参构造方法,分别如下:

public class CyclicBarrier {

	private final ReentrantLock lock = new ReentrantLock();
	private final Condition trip = lock.newCondition();

	private final int parties;
	private final Runnable barrierCommand;
	
	/** 
	 * CyclicBarrier 是可循环使用的屏障,这里使用 Generation 记录当前轮次 CyclicBarrier 
	 * 的运行状态。当所有线程到达屏障后,generation 将会被更新,表示 CyclicBarrier 进入新一
	 * 轮的运行轮次中。
	*/
	private Generation generation = new Generation();
	private static class Generation {
        boolean broken;         // initially false
    }
	
	
	public class CyclicBarrier {
	/** 创建一个允许 parties 个线程通行的屏障 */
	public CyclicBarrier(int parties) {
		this(parties, null);
	}

	/** 
	 * 创建一个允许 parties 个线程通行的屏障,若 barrierAction 回调对象不为 null,
	 * 则在最后一个线程到达屏障后,执行相应的回调逻辑
	 */
	public CyclicBarrier(int parties, Runnable barrierAction) {
		if (parties <= 0) throw new IllegalArgumentException();
		this.parties = parties;
		this.count = parties;
		this.barrierCommand = barrierAction;
	}
}

上面的第二个构造方法初始化了一些成员变量,下面我们就来说明一下这些成员变量的作用。

成员变量 作用
parties 线程数,即当 parties 个线程到达屏障后,屏障才会放行
count 计数器,当 count > 0 时,到达屏障的线程会进入等待状态。当最后一个线程到达屏障后,count 自减至0。最后一个到达的线程会执行回调方法,并唤醒其他处于等待状态中的线程。
barrierCommand 回调对象,如果不为 null,会在第 parties 个线程到达屏障后被执行

2.2 await

基本过程:

  1. 如果当前线程不是最后一个线程,调用该方法,那么就在这里一直等待;
  2. 直到最后一个线程调用该方法,执行附加指令后,通知所有其他线程。
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        // await 的逻辑封装在 dowait 中
        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;

        // 如果 g.broken = true,表明屏障被破坏了,这里直接抛出异常
        if (g.broken)
            throw new BrokenBarrierException();

        // 如果线程中断,则调用 breakBarrier 破坏屏障
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }

        /*
         * index 表示线程到达屏障的顺序,index = parties - 1 表明当前线程是第一个
         * 到达屏障的。index = 0,表明当前线程是最有一个到达屏障的。
         */ 
        int index = --count;
		
        // 当 index = 0 时,唤醒所有处于等待状态的线程
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                // 如果回调对象不为 null,则执行回调
                if (command != null)
                    command.run();
                ranAction = true;
                // 重置屏障状态,使其进入新一轮的运行过程中
                nextGeneration();
                return 0;
            } finally {
                // 若执行回调的过程中发生异常,此时调用 breakBarrier 破坏屏障
                if (!ranAction)
                    breakBarrier();
            }
        }

        // 线程运行到此处的线程都会被屏障挡住,并进入等待状态。
        for (;;) {
            try {
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                /*
                 * 若下面的条件成立,则表明本轮运行还未结束。此时调用 breakBarrier 
                 * 破坏屏障,唤醒其他线程,并抛出异常
                 */ 
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    /*
                     * 若上面的条件不成立,则有两种可能:
                     * 1. g != generation
                     *     此种情况下,表明循环屏障的第 g 轮次的运行已经结束,屏障已经
                     *     进入了新的一轮运行轮次中。当前线程在稍后返回 到达屏障 的顺序即可
                     *     
                     * 2. g = generation 但 g.broken = true
                     *     此种情况下,表明已经有线程执行过 breakBarrier 方法了,当前
                     *     线程则会在稍后抛出 BrokenBarrierException
                     */
                    Thread.currentThread().interrupt();
                }
            }
            
            // 屏障被破坏,则抛出 BrokenBarrierException 异常
            if (g.broken)
                throw new BrokenBarrierException();

            // 屏障进入新的运行轮次,此时返回线程在上一轮次到达屏障的顺序
            if (g != generation)
                return index;

            // 超时判断
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}
    
/** 开启新的一轮运行过程 */
private void nextGeneration() {
    // 【唤醒所有处于等待状态中的线程】
    trip.signalAll();
    // 重置 count
    count = parties;
    // 重新创建 Generation,表明进入循环屏障进入新的一轮运行轮次中
    generation = new Generation();
}

/** 破坏屏障 */
private void breakBarrier() {
    // 设置屏障是否被破坏标志
    generation.broken = true;
    // 重置 count
    count = parties;
    // 唤醒所有处于等待状态中的线程
    trip.signalAll();
}

2.3 reset

reset 方法用于强制重置屏障,使屏障进入新一轮的运行过程中。代码如下:

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 破坏屏障
        breakBarrier();   // break the current generation
        // 开启新一轮的运行过程
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}

三、CountDownLatch 和 CyclicBarrier

CountDownLatch 允许一个或一组线程等待其他线程完成后再恢复运行。线程可通过调用await方法进入等待状态,在其他线程调用countDown方法将计数器减为0后,处于等待状态的线程即可恢复运行。

CyclicBarrier (可循环使用的屏障)则与此不同,CyclicBarrier 允许一组线程到达屏障后阻塞住,直到最后一个线程进入到达屏障,所有线程才恢复运行。

它们之间主要的区别在于唤醒等待线程的时机:

CountDownLatch 是在计数器减为0后,唤醒等待线程。CyclicBarrier 是在计数器(等待线程数)增长到指定数量后,再唤醒等待线程

差异点 CountDownLatch CyclicBarrier
是否可循环使用
是否可设置回调

猜你喜欢

转载自blog.csdn.net/fei20121106/article/details/83301771
今日推荐