CyclicBarrier使用与原理

 

 

1 CyclicBarrier简介
CyclicBarrier,是JDK1.5的java.util.concurrent并发包中提供的一个并发工具类。

所谓Cyclic即 循环 的意思,所谓Barrier即 屏障 的意思。

所以综合起来,CyclicBarrier指的就是 循环屏障,虽然这个叫法很奇怪,但是确能很好地表示它的作用。

其作用在JDK注释中是这样描述的:

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. 
CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. 
The barrier is called cyclic because it can be re-used after the waiting threads are released.

翻译过来,如下:

CyclicBarrier是一个同步辅助类,它允许一组线程相互等待直到所有线程都到达一个公共的屏障点。
在程序中有固定数量的线程,这些线程有时候必须等待彼此,这种情况下,使用CyclicBarrier很有帮助。
这个屏障之所以用循环修饰,是因为在所有的线程释放彼此之后,这个屏障是可以重新使用的。
CyclicBarrier的简单理解

其实,我更喜欢[人满发车]这个词来理解CyclicBarrier的作用:

长途汽车站提供长途客运服务。
当等待坐车的乘客到达20人时,汽车站就会发出一辆长途汽车,让这20个乘客上车走人。
等到下次等待的乘客又到达20人是,汽车站就会又发出一辆长途汽车。

2.CyclicBarrier方法说明
CyclicBarrier提供的方法有:

——CyclicBarrier(parties)

初始化相互等待的线程数量的构造方法。

——CyclicBarrier(parties,Runnable barrierAction)

初始化相互等待的线程数量以及屏障线程的构造方法。

屏障线程的运行时机:等待的线程数量=parties之后,CyclicBarrier打开屏障之前。

举例:在分组计算中,每个线程负责一部分计算,最终这些线程计算结束之后,交由屏障线程进行汇总计算。

——getParties()

获取CyclicBarrier打开屏障的线程数量,也成为方数。

——getNumberWaiting()

获取正在CyclicBarrier上等待的线程数量。

——await()

在CyclicBarrier上进行阻塞等待,直到发生以下情形之一:

  • 在CyclicBarrier上等待的线程数量达到parties,则所有线程被释放,继续执行。
  • 当前线程被中断,则抛出InterruptedException异常,并停止等待,继续执行。
  • 其他等待的线程被中断,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他等待的线程超时,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他线程调用CyclicBarrier.reset()方法,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。

——await(timeout,TimeUnit)

在CyclicBarrier上进行限时的阻塞等待,直到发生以下情形之一:

  • 在CyclicBarrier上等待的线程数量达到parties,则所有线程被释放,继续执行。
  • 当前线程被中断,则抛出InterruptedException异常,并停止等待,继续执行。
  • 当前线程等待超时,则抛出TimeoutException异常,并停止等待,继续执行。
  • 其他等待的线程被中断,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他等待的线程超时,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他线程调用CyclicBarrier.reset()方法,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。

——isBroken()

获取是否破损标志位broken的值,此值有以下几种情况:

  • CyclicBarrier初始化时,broken=false,表示屏障未破损。
  • 如果正在等待的线程被中断,则broken=true,表示屏障破损。
  • 如果正在等待的线程超时,则broken=true,表示屏障破损。
  • 如果有线程调用CyclicBarrier.reset()方法,则broken=false,表示屏障回到未破损状态。

——reset()

使得CyclicBarrier回归初始状态,直观来看它做了两件事:

  • 如果有正在等待的线程,则会抛出BrokenBarrierException异常,且这些线程停止等待,继续执行。
  • 将是否破损标志位broken置为false。

3 原理

首先,看一下CyclicBarrier内声明的一些属性信息

 

//用于保护屏障入口的锁
private final ReentrantLock lock = new ReentrantLock();
//线程等待条件
private final Condition trip = lock.newCondition();
//记录参与等待的线程数
private final int parties;
//当所有线程到达屏障点之后,首先执行的命令
private final Runnable barrierCommand;
private Generation generation = new Generation();
//实际中仍在等待的线程数,每当有一个线程到达屏障点,count值就会减一;当一次新的运算开始后,count的值被重置为parties
private int count;

 其中,Generation是CyclicBarrier的一个静态内部类,它只有一个boolean类型的属性,具体代码如下:

private static class Generation {
        boolean broken = false;
    }

 当使用构造方法创建CyclicBarrier实例的时候,就是给上面这些属性赋值,

//创建一个CyclicBarrier实例,parties指定参与相互等待的线程数,
   //barrierAction指定当所有线程到达屏障点之后,首先执行的操作,该操作由最后一个进入屏障点的线程执行。
   public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

    //创建一个CyclicBarrier实例,parties指定参与相互等待的线程数
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

CyclicBarrier.await方法调用CyclicBarrier.dowait(),每次调用await()都会使计数器-1,当减少到0时就会唤醒所有的线程 ,当调用await()方法时,当前线程已经到达屏障点,当前线程阻塞进入休眠状态。

 //该方法被调用时表示当前线程已经到达屏障点,当前线程阻塞进入休眠状态
    //直到所有线程都到达屏障点,当前线程才会被唤醒
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen;
        }
    }

    

当前线程已经到达屏障点,当前线程阻塞进入休眠状态 

//该方法被调用时表示当前线程已经到达屏障点,当前线程阻塞进入休眠状态
    //在timeout指定的超时时间内,等待其他参与线程到达屏障点
    //如果超出指定的等待时间,则抛出TimeoutException异常,如果该时间小于等于零,则此方法根本不会等待
    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }
 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();
            }
// gavin 这里是关键
           //每调用一次await()方法,计数器就减一
           int index = --count;
           //当计数器值等于0的时
           if (index == 0) {  // tripped
               boolean ranAction = false;
               try {
                   final Runnable command = barrierCommand;
                   //如果在创建CyclicBarrier实例时设置了barrierAction,则先执行barrierAction
                   if (command != null)
                       command.run();
                   ranAction = true;
                   //当所有参与的线程都到达屏障点,为唤醒所有处于休眠状态的线程做准备工作
                   //需要注意的是,唤醒所有阻塞线程不是在这里
                   nextGeneration();
                   return 0;
               } finally {
                   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 {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        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();
        }
    }

最核心的部分就是

每次调用await方法都会使内部的计数器临时变量-1,当减少到0时,就会调用nextGeneration方法

 private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

在这里唤醒所有阻塞的线程

提醒:在声明CyclicBarrier的时候还可以传一个Runnable的实现类,当计数器减少到0时,会执行该实现类

4 线程阻塞

到这里CyclicBarrier的实现原理基本已经都清楚了,下面来深入源码分析一下线程阻塞代码trip.await()和线程唤醒trip.signalAll()的实现。

//await()是AQS内部类ConditionObject中的方法
        public final void await() throws InterruptedException {
            //如果线程中断抛异常
            if (Thread.interrupted())
                throw new InterruptedException();
            //新建Node节点,并将新节点加入到Condition等待队列中
            //Condition等待队列是AQS内部类ConditionObject实现的,ConditionObject有两个属性,分别是firstWaiter和lastWaiter,都是Node类型
            //firstWaiter和lastWaiter分别用于代表Condition等待队列的头结点和尾节点
            Node node = addConditionWaiter();
            //释放独占锁,让其它线程可以获取到dowait()方法中的独占锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //检测此节点是否在资源等待队列(AQS同步队列)中,
            //如果不在,说明此线程还没有竞争资源锁的权利,此线程继续阻塞,直到检测到此节点在资源等待队列上(AQS同步队列)中
            //这里出现了两个等待队列,分别是Condition等待队列和AQS资源锁等待队列(或者说是同步队列)
            //Condition等待队列是等待被唤醒的线程队列,AQS资源锁等待队列是等待获取资源锁的队列
            while (!isOnSyncQueue(node)) {
                //阻塞当前线程,当前线程进入休眠状态,可以看到这里使用LockSupport.park阻塞当前线程
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

        //addConditionWaiter()是AQS内部类ConditionObject中的方法
        private Node addConditionWaiter() {
            Node t = lastWaiter;
            // 将condition等待队列中,节点状态不是CONDITION的节点,从condition等待队列中移除
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //以下操作是用此线程构造一个节点,并将之加入到condition等待队列尾部
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
        
        //signalAll是AQS内部类ConditionObject中的方法
        public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //Condition等待队列的头结点
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }
        
        private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                //将Condition等待队列中的Node节点按之前顺序都转移到了AQS同步队列中
                transferForSignal(first);
                first = next;
            } while (first != null);
        }

        final boolean transferForSignal(Node node) {
            if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
               return false;
            //这里将Condition等待队列中的Node节点插入到AQS同步队列的尾部
            Node p = enq(node);
            int ws = p.waitStatus;
            if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
               LockSupport.unpark(node.thread);
            return true;
       }

       //ReentrantLock#unlock()方法
       public void unlock() {
           //Sync是ReentrantLock的内部类,继承自AbstractQueuedSynchronizer,它是ReentrantLock中公平锁和非公平锁的基础实现
           sync.release(1);
       }

       public final boolean release(int arg) {
           //释放锁
           if (tryRelease(arg)) {
               //AQS同步队列头结点
               Node h = head;
               if (h != null && h.waitStatus != 0)
                   //唤醒节点中的线程
                   unparkSuccessor(h);
               return true;
           }
           return false;
       }

       private void unparkSuccessor(Node node) {
           int ws = node.waitStatus;
           if (ws < 0)
               compareAndSetWaitStatus(node, ws, 0);
           Node s = node.next;
           if (s == null || s.waitStatus > 0) {
               s = null;
               for (Node t = tail; t != null && t != node; t = t.prev)
                   if (t.waitStatus <= 0)
                       s = t;
           }
           if (s != null)
               //唤醒阻塞线程
               LockSupport.unpark(s.thread);
    }     

5 原理总结

用上面的示例总结一下CyclicBarrier的await方法实现,假设线程thread1和线程thread2都执行到CyclicBarrier的await(),都进入dowait(boolean timed, long nanos),thread1先获取到独占锁,执行到--count的时,index等于1,所以进入下面的for循环,接着执行trip.await(),进入await()方法,执行Node node = addConditionWaiter()将当前线程构造成Node节点并加入到Condition等待队列中,然后释放获取到的独占锁,当前线程进入阻塞状态;此时,线程thread2可以获取独占锁,继续执行--count,index等于0,所以先执行command.run(),输出myThread,然后执行nextGeneration(),nextGeneration()中trip.signalAll()只是将Condition等待队列中的Node节点按之前顺序都转移到了AQS同步队列中,这里也就是将thread1对应的Node节点转移到了AQS同步队列中,thread2执行完nextGeneration(),返回return 0之前,细看代码还需要执行lock.unlock(),这里会执行到ReentrantLock的unlock()方法,最终执行到AQS的unparkSuccessor(Node node)方法,从AQS同步队列中的头结点开始释放节点,唤醒节点对应的线程,即thread1恢复执行。

如果有三个线程thread1、thread2和thread3,假设线程执行顺序是thread1、thread2、thread3,那么thread1、thread2对应的Node节点会被加入到Condition等待队列中,当thread3执行的时候,会将thread1、thread2对应的Node节点按thread1、thread2顺序转移到AQS同步队列中,thread3执行lock.unlock()的时候,会先唤醒thread1,thread1恢复继续执行,thread1执行到lock.unlock()的时候会唤醒thread2恢复执行。

6 例子

package com.gavin.thread;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
 * 
 * 
 * @author gavin
 *
 */
public class TestCyclicBarrier {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception  {
		 //构造函数1:初始化-开启屏障的方数
	final	CyclicBarrier barrier0 = new CyclicBarrier(2);
		//通过barrier.getParties()获取开启屏障的方数
		LOGGER.info("barrier.getParties()获取开启屏障的方数:" + barrier0.getParties());
		//通过barrier.getNumberWaiting()获取正在等待的线程数
		LOGGER.info("通过barrier.getNumberWaiting()获取正在等待的线程数:初始----" + barrier0.getNumberWaiting());
		System.out.println();

		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				 LOGGER.info("添加第1个等待线程----" + Thread.currentThread().getName());
				    try {
				        barrier0.await();
				        LOGGER.info(Thread.currentThread().getName() + " is running...");
				    } catch (InterruptedException e) {
				        e.printStackTrace();
				    } catch (BrokenBarrierException e) {
				        e.printStackTrace();
				    }
				    LOGGER.info(Thread.currentThread().getName() + " is terminated.");
			}
		})
		.start();
		Thread.sleep(10);
		//通过barrier.getNumberWaiting()获取正在等待的线程数
//		LOGGER.info();
		System.out.println("通过barrier.getNumberWaiting()获取正在等待的线程数:添加第1个等待线程---" + barrier0.getNumberWaiting());
		Thread.sleep(10);
		System.out.println();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				//添加一个等待线程
			    LOGGER.info("添加第2个等待线程----" + Thread.currentThread().getName());
			    try {
			        barrier0.await();
			        LOGGER.info(Thread.currentThread().getName() + " is running...");
			    } catch (InterruptedException e) {
			        e.printStackTrace();
			    } catch (BrokenBarrierException e) {
			        e.printStackTrace();
			    }
			    LOGGER.info(Thread.currentThread().getName() + " is terminated.");
			}
		}).start();
		Thread.sleep(100);
		System.out.println();
		//通过barrier.getNumberWaiting()获取正在等待的线程数
		LOGGER.info("通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---" + barrier0.getNumberWaiting());

		//已经打开的屏障,再次有线程等待的话,还会重新生效--视为循环
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				LOGGER.info("屏障打开之后,再有线程加入等待:" + Thread.currentThread().getName());
			    try {
			        //BrokenBarrierException
			        barrier0.await();
			    } catch (InterruptedException e) {
			        e.printStackTrace();
			    } catch (BrokenBarrierException e) {
			        e.printStackTrace();
			    }
			    LOGGER.info(Thread.currentThread().getName() + " is terminated.");	
			}
		}).start();
		System.out.println();
		Thread.sleep(10);
		LOGGER.info("通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---" + barrier0.getNumberWaiting());
		Thread.sleep(10);
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				LOGGER.info("屏障打开之后,再有线程加入等待:" + Thread.currentThread().getName());
			    try {
			        //BrokenBarrierException
			        barrier0.await();
			    } catch (InterruptedException e) {
			        e.printStackTrace();
			    } catch (BrokenBarrierException e) {
			        e.printStackTrace();
			    }
			    LOGGER.info(Thread.currentThread().getName() + " is terminated.");	
			}
		}).start();
		Thread.sleep(10);
		LOGGER.info("通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---" + barrier0.getNumberWaiting());
	}
 static class LOGGER{
	 public static void info(String res){
		 System.out.println(res);
	 }
 }
}

运行效果

 

barrier.getParties()获取开启屏障的方数:2
通过barrier.getNumberWaiting()获取正在等待的线程数:初始----0

添加第1个等待线程----Thread-0
通过barrier.getNumberWaiting()获取正在等待的线程数:添加第1个等待线程---1

添加第2个等待线程----Thread-1
Thread-1 is running...
Thread-1 is terminated.
Thread-0 is running...
Thread-0 is terminated.

通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---0

屏障打开之后,再有线程加入等待:Thread-2
通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---1
屏障打开之后,再有线程加入等待:Thread-3
Thread-3 is terminated.
Thread-2 is terminated.
通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---0

 

 面试考点

CyclicBarrier当所有线程都到达屏障点后,等待线程的执行顺序是什么样的?

CyclicBarrier的await方法是使用ReentrantLock和Condition控制实现的,使用的Condition实现类是ConditionObject,它里面有一个等待队列和await方法,这个await方法会向队列中加入元素。当调用CyclicBarrier的await方法会间接调用ConditionObject的await方法,当屏障关闭后首先执行指定的barrierAction,然后依次执行等待队列中的任务,有先后顺序。

 

参考地址

https://my.oschina.net/u/3049601/blog/1808546

https://blog.csdn.net/hanchao5272/article/details/79779639

https://blog.csdn.net/hanchao5272/article/details/79779639

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值