Java并发编程之CyclicBarrier

Java并发编程之CyclicBarrier

**CyclicBarrier简介:**CyclicBarrier翻译成中文是可循环使用的屏障。它与互斥量、读写锁、自旋锁不同,它不是用来保护临界区的。相反,它跟条件变量一样,是用来协同多线程一起工作的。条件变量是多线程间传递状态的改变来达到协同工作的效果。CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
在这里插入图片描述
CyclicBarrier适用场景:
1.几件事情完成之后才能开始另外一件事情。
2.需要做的几件事情可以独立完成,并且可以并行处理。
3.以上事情完成后继续下一轮处理。

CyclicBarrier主要方法:

1、构造函数:CyclicBarrier类提供两个构造方法,默认构造方法是第一个。parties表示屏障拦截的线程数量,每个线程使用await()方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。但它不会在启动 barrier 时执行预定义的操作。带有两个参数的构造方法,第二个参数barrierAction表示在所有线程到达屏障时启动给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

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;
}

2、调用await()方法的线程告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞。直到有parties个线程调用了await方法,CyclicBarrier提供带超时时间的await和不带超时时间的await方法。

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        // 不超时等待
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
public int await(long timeout, TimeUnit unit)
    throws InterruptedException,
            BrokenBarrierException,
            TimeoutException {
    return dowait(true, unit.toNanos(timeout));
}

3、dowait()详解:
(1)、首先通过ReentrantLock获取独占锁。
(2)、通过try里面的Generation判断当前代是否损坏
(3)、通过Thread的interrupted方法判断是否线程中断,如果中断通过breakBarrier方法告诉其他线程。
(4)、if(index==0)判断当前是否是最后一个线程调用了await方法,如果是则把之前等待的线程全部唤醒。就好比是最后一个运动员到达了终点,告诉其他选手比赛结束了。
(5)、for(;;)循环执行等待,如果没有超时时间,那就一直等待直到被唤醒,有超时时间,那就等时间过后自动被唤醒。就好比是在运动员在路上跑步,没有时间限制的时候那就一直跑,一直到达终点。如果有时间限制,不管是否跑到终点,比赛都结束。
(6)、通过ReentrantLock释放独占锁。

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();
    }
}

4、isBroken()方法用来查询此屏障是否处于损坏状态。如果因为构造或最后一次重置而导致中断或超时,从而使一个或多个参与者摆脱此 barrier,或者因为异常而导致某个屏障操作失败,则返回 true;否则返回 false。

//查询当前barrier是否处于broken状态.
        public boolean isBroken() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return generation.broken;
            } finally {
                lock.unlock();
            }
        }

5、reset()方法用于将屏障重置为其初始状态。如果所有参与者目前都在屏障处等待,则它们将返回,同时抛出一个 BrokenBarrierException。(注意,在由于其他原因造成损坏之后,实行重置可能会变得很复杂;此时需要使用其他方式重新同步线程,并选择其中一个线程来执行重置。与为后续使用创建一个新 barrier 相比,这种方法可能更好一些。 )

public void reset() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                breakBarrier();   // break the current generation
                nextGeneration(); // start a new generation
            } finally {
                lock.unlock();
            }
        }

6、getNumberWaiting() 方法返回当前在屏障处等待的参与者数目。

 public int getNumberWaiting() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return parties - count;
            } finally {
                lock.unlock();
            }
        }

7、getParties()方法返回要求启动此 barrier 的参与者数目。

//返回barrier需要发生trip的线程数目.
        public int getParties() {
            return parties;
        }

CyclicBarrier和CountDownLatch的区别

CountDownLatch: 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行并且计数器只能使用一次。
CyclicBarrier: N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。CyclicBarrier的计数器可以使用reset()方法重置,所以CyclicBarrier能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数器,并让线程重新执行一次。

本文参考
本文主要参考以下文章,谨以技术分享为目的,将此文搬到CSDN上,如有侵权问题请联系本人,乐于分享提高。
作者: DivineH
链接:https://blog.csdn.net/qq_38293564/article/details/80558157

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值