并发编程学习笔记之十二并发工具CyclicBarrier循环栅栏

一、CyclicBarrier简介

CyclicBarrier循环栅栏是让一组线程达到一个栅栏(屏障)时被阻塞,直到最后一个线程到达栅栏时,栅栏才会开门。简单的说CyclicBarrier也是一个计数器,不过这个技术器有一个栅栏,等所有线程都到达栅栏后才会去继续执行后面的程序。

CyclicBarrier循环栅栏和闭锁CountDownLatch很像,不过和闭锁的主要区别在于,所有线程必须同时到达屏障点,才能继续执行,闭锁用于等待事件,而栅栏用于实现一些协议。比如:长途大巴行驶到服务器,自由活动20分钟,之后司机在等待最后一个乘客上车之后,才会出发驶出服务区,开始新的行程。

二、使用案例:

在下面的例子中,栅栏cb设置为2,执行程序会看到打印结果为1和2或2和1,造成这样的原因的是线程的执行顺序是由CPU决定的。至此好像也没有体现出CyclicBarrier的特点。不过当cb的初识值设置为2以上时,比如说3,再执行程序时,程序会一直阻塞下去。因为本身就没有第三条线程会执行,所有就会一直等待。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * CyclicBarrier循环栅栏
 * 使用案例
 */
public class CyclicBarrierTest {
    static CyclicBarrier cb = new CyclicBarrier(2);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    cb.await();
                    System.out.println(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        try {
            cb.await();
            System.out.println(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

 三、内部结构及实现原理:

来看下CyclicBarrier的内部结构,一探究竟。

内部类:当前栅栏,仅包含一个状态值,默认为false(栅栏完成),当线程超时或者中断时,会被修改为true,表示栅栏已经被破坏。

    private static class Generation {
        boolean broken = false;
    }

构造方法:

    //包含栅栏的大小和有继续执行任务
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
    //只包含栅栏大小,调用CyclicBarrier(int parties, Runnable barrierAction),
    继续执行任务设置为null
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

重要的成员变量: 

    /** 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,初始值就是栅栏的大小paities,
    //每有一个线程到达栅栏都会自减1
    private int count;

主要方法:

 进入栅栏:无论是否含有参数,进入栅栏都是通过可重入锁ReentrantLock来锁住栅栏的入口。

//不含参数
public int await() throws InterruptedException, BrokenBarrierException {...}
//含有超时时间
public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {

1-1无论是否设置超时时间,调用的方法都是dowait方法,首先先加锁,判断栅栏的状态是正常,如果已经损坏在抛错BrokenBarrierException。继续判断线程是否中断,如果线程已经中断,先修改栈蓝状态为损坏,同时抛错InterruptedException。

1-2倒计数,如果一个线程到达栅栏则自减1,如果此时index==0,说明所有线程都到达了栅栏,如果还有线程其他任务执行则barrierCommand线程任务。紧接着执行nextGeneration(),唤醒所有线程继续执行,恢复派对数量,同时更新了生成一个新的栅栏。需要注意的是必须是持有同一把锁期间。如果再唤醒所有线程之前出现异常,则调用breakBarrier()方法,唤醒所有对象,将当前的栅栏状态更新为已损坏。

1-3、自旋直至跳闸、断裂、中断或超时,接着判断栅栏是否被破坏,如果已被破坏则抛错BrokenBarrierException ,超时判断,如果超时则调用breakBarrier()方法,唤醒所有对象,将当前的栅栏状态更新为已损坏,抛错超时异常TimeoutException,最后释放锁。

    //进入栅栏
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
    //1-1
    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-2步骤
            int index = --count;//派对数量自减1
            //表示所有线程都已阻塞在栅栏
            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();
                }
            }

            // 1-3 自旋直至跳闸、断裂、中断或超时
            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();
                    }
                }
                //判断栅栏状态
                if (g.broken)
                    throw new BrokenBarrierException();
                //是否是同一个栅栏
                if (g != generation)
                    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();
    }

重置栅栏: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();
        }
    }

 

 参考书籍:

方志明 著《Java并发编程艺术》

Doug lea 等著《Java并发编程实战》 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值