Java并发——循环栅栏CyclicBarrier源码解析

版权声明:个人博客:blog.suyeq.com 欢迎访问 https://blog.csdn.net/hackersuye/article/details/85009854

CyclicBarrier

    从CyclicBarrier类的名字来看,它是循环栅栏,是栅栏机制的一种实现,它在内部利用ReentrantLock锁以及Condition的线程协调机制来实现这一功能。简单的说下应用场景,假如有三个线程,我们需要当这三个线程都到达某一位置时,才执行下一步,这时我们就可以用CyclicBarrier类来实现这个功能,也就是说CyclicBarrier构建了一个类似于栅栏的事物,来挡住线程的下一步执行,当它要求的所有线程都到了指定的位置的时候,那么才打开栅栏,让这三个线程继续执行,只要有一个线程没到那么就不打开。

    栅栏机制听起来有点像闭锁CountDownLatch类,但是却不相同,CountDownLatch类可以实现让一部分线程去等待另一部分线程执行完才去执行,而栅栏CyclicBarrier只是线程间的相互等待,只有全部到了指定位置,才能去执行下一步。栅栏的应用如下面示例代码:

public class Test {
    public static void main (String args[]) throws InterruptedException {
        CyclicBarrier cyclicBarrier=new CyclicBarrier(2, new Runnable() {
            @Override
            public void run() {
                System.out.println("艾米莉亚开始执行了");
            }
        });
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    System.out.println("蕾姆到达指定地点啦");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    System.out.println("拉姆到达指定地点啦");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}
//输出:
//拉姆到达指定地点啦
//蕾姆到达指定地点啦
//艾米莉亚开始执行了

    从代码中看出,当thread1thread2到达了指定位置时,才会执行预先定好的输出艾米莉亚开始执行了的方法。我们来深入CyclicBarrier类的源码看看,首先是它的构造方法:

public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

    parties代表着可以让多少个线程互相等待,barrierCommand代表着当全部线程到达指定位置时执行的预定义方法。CyclicBarrier在源码开头定义了如下代码:

	private static class Generation {
        boolean broken = false;
    }
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition trip = lock.newCondition();

    可以看到利用ReentrantLock锁来保持原子性,利用Condition来进行线程间的协调通信,Generation内部类代表着栅栏是否被打破,默认是false没有打破,当运行完成后会把这个属性设置成true,即打破栅栏。CyclicBarrier类一个很重要的属性就是可以重用,也就是说当此次栅栏完成后会自动调用nextGeneration方法进行重新设置状态就可以再次使用,当然你也可以手动调用reset方法:

//打破栅栏
private void breakBarrier() {
		//将栅栏的状态设置为打破状态
        generation.broken = true;
        //未到达的线程重新设置成初始化大小
        count = parties;
        //唤醒处在等待队列中的线程
        trip.signalAll();
    }
    //重新设置属性
    private void nextGeneration() {
        trip.signalAll();
        count = parties;
        generation = new Generation();
    }
public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }

    我们常说的让所有相互等待的线程都到了某个点时,才执行下一步,这个点其实是调用await方法的地方,而await方法调用的是dowait方法:

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()) {
                breakBarrier();
                throw new InterruptedException();
            }
            //每次调用都会讲未到达的线程减1,因为线程到了指定的点了
            int index = --count;
            //所有的线程都到达指定的点
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                //执行预设的方法
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //重置属性,唤醒所有处于等待队列中的线程
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
            for (;;) {
            	//如果有线程没有达到,那么当前线程进入等待队列
                try {
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
               //......
        } finally {
            lock.unlock();
        }
    }

    从源码中可以看出,只要有线程中断,那么就会打破栅栏,当有线程中调用了await方法都会将未到达的线程数减1,如果还有线程未到达,那么当前线程就会陷入等待,当最后一个线程达到时,将调用nextGeneration方法,唤醒所有的线程,所有的线程接着运行。

猜你喜欢

转载自blog.csdn.net/hackersuye/article/details/85009854