深入理解 CyclicBarrier【源码分析】

CyclicBarrier 概念:

CyclicBarrier 字面意思是环栅栏,是 JUC 下的一个并发工具,跟 CountDownLatch 很相似,都可以使线程先等待然后再执行,但是它的功能比 CountDownLatch 更加复杂和强大, CountDownLatch 是一个或者多个线程等待另外一批线程执行完毕后,在接着执行,而 CyclicBarrier 是等待一批线程到达某个状态之后再同时开始执行,回环的意思是当所有的线程被释放后,CyclicBarrier 可以被重启,也就是可以重复使用。

知识储备传送门:

深入理解 AbstractQueuedSynchronizer(AQS)【源码分析】
深入理解 ReentrantLock 【源码分析】
CAS的使用以及底层原理详解
深入理解 CountDownLatch 【源码分析】

CyclicBarrier 属性、构造方法源码分析:

public class CyclicBarrier {
    
	//内部类 每次使用CyclicBarrier 都会生成一个Generation实例 每当屏障被处罚或者重置时候 对应的Generation就会发生变化
    private static class Generation {
        boolean broken = false;
    }

	//ReentrantLock 重入锁 保护屏障入口的锁
    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();

	//还要等待的线程数量 也就是还能阻塞的线程数 还有多少线程为到屏障前 
    private int count;

    //更新屏障行程状态并唤醒所有线程 仅在持有锁时调用 是开启下一代的方法
    private void nextGeneration() {
        // signal completion of last generation
		//唤醒所有线程
        trip.signalAll();
        // set up next generation
		//恢复count值,开启新的一代
        count = parties;
        generation = new Generation();
    }

   //将当前屏障生成设置为已破坏并唤醒所有线程 仅在持有锁时调用。
    private void breakBarrier() {
		//将这一代中的 broken 设置为true  表示这一代是被打破了的 再来到这一代的线程  直接抛出异常
        generation.broken = true;
		//重置 count
        count = parties;
		//唤醒所有线程 调用的是 AbstractQueuedLongSynchronizer.ConditionObject#signalAll
        trip.signalAll();
    }
	
	//构造方法
	public CyclicBarrier(int parties, Runnable barrierAction) {
		//如果拦截的线程数量小于0 就抛出异常
        if (parties <= 0) throw new IllegalArgumentException();
		//拦截的线程数量等于传入的数量
        this.parties = parties;
		//还要等待的线程数量
        this.count = parties;
		//设置所有线程都到达屏障位置要执行的操作
        this.barrierCommand = barrierAction;
    }

    //构造方法 创建拦截指定数量线程的CyclicBarrier
    public CyclicBarrier(int parties) {
        this(parties, null);
    }
}

类属性解析:

  • Generation:代,在 CyclicBarrier 中同一批线程属于同一代,当屏障拦截的线程数 parties 都到达屏障前,此时会更新到下一代,broken 标识当前代的状态,默认 false ,false 表示没有被打破,true 表示被打破了。
  • parties:屏障拦截的线程数。
  • count:没有达到屏障前的线程数,也就是要等待的线程数。
  • trip :等待屏障触发的条件,用于线程的等待或者唤醒。
  • lock :重入锁,保护屏障入口的锁。
  • barrierCommand:屏障将要放开的时候执行的操作,也就是所有等待的线程都到达屏障了之后的操作。
  • nextGeneration:开启下一代的方法,并唤醒所有等待的线程。
  • breakBarrier:将当前屏障生成设置为已破坏,并唤醒所有线程,仅在持有锁时才能调用。

AbstractQueuedLongSynchronizer.ConditionObject#signalAll 方法源码分析:

//AbstractQueuedLongSynchronizer.ConditionObject#signalAll 唤醒所有的线程
public final void signalAll() {
	//当前线程是否占有锁 必须占有锁才可以调用  doSignalAll 方法
	if (!isHeldExclusively())
		throw new IllegalMonitorStateException();
	//获取头节点
	Node first = firstWaiter;
	//头节点不为空 表示是正常的条件队列
	if (first != null)
		//执行唤醒操作
		doSignalAll(first);
}

//java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObject#doSignalAll 删除并转移所有节点 其实就是唤醒所有节点并删除
private void doSignalAll(Node first) {
	//最后一个节点 第一个节点都赋值为 null
	lastWaiter = firstWaiter = null;
	//循环唤醒节点
	do {
		//获取头节点的下一个节点
		Node next = first.nextWaiter;
		//头节点的下一个赋值为 null
		first.nextWaiter = null;
		//
		transferForSignal(first);
		first = next;
	} while (first != null);
}

//java.util.concurrent.locks.AbstractQueuedLongSynchronizer#transferForSignal 将节点从条件队列传输到同步队列 如果成功则返回 true。
final boolean transferForSignal(Node node) {
	
	//将节点状态设置为 0 
	if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
		//设置失败 
		return false;

   
	//将当前节点添加到同步阻塞队列中 AQS 源码中分析过  enq 方法
	Node p = enq(node);
	//获取节点的状态
	int ws = p.waitStatus;
	//状态大于0 表示节点已经取消了 或者尝试设置节点P的状态为 SIGNAL 失败了(表示下一个节点要停止阻塞 需要唤醒他了) 
	if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
		//唤醒当前线程上的节点
		LockSupport.unpark(node.thread);
	return true;
}

//AbstractQueuedLongSynchronizer#enq 初始化队列 并把node 节点加入队列作为队列的尾节点
private Node enq(final Node node) {
	//自旋
	for (;;) {
		//把尾节点 赋值给 t
		Node t = tail;
		//判断尾节点是否为空
		if (t == null) { // Must initialize
			//尾节点为空 创建一个新的空节点 并使用 CAS 将创建的空节点设置为队列头节点
			if (compareAndSetHead(new Node()))
				//头节点设置成功后 也就是队列初始化成功后 把队列的尾节点也指向刚刚创建的空的头节点
				tail = head;
		} else {
			//尾节点不为空 表示队列初始化完成了 至少是第二次循环了  将node节点的前驱指针指向当前队列的尾节点
			node.prev = t;
			//使用 CAS 用设置node 节点为新的尾节点
			if (compareAndSetTail(t, node)) {
				//设置成功后 将之前的尾节点 t 的后驱指针指向 node 节点
				t.next = node;
				//node 节点成功加入到队列尾部 
				return t;
			}
		}
	}
}

CyclicBarrier #await 方法源码分析:

调用 await 方法的线程告诉 CyclicBarrier 我已经到达屏障位置,然后阻塞当前线程等待其他线程的到来,CyclicBarrier 提供带超时的 await 方法和不带超时时间的await方法,这两个方法最后都会调用 dowait 方法。

//不带超时的await方法
public int await() throws InterruptedException, BrokenBarrierException {
	try {
		//真正干活的方法
		return dowait(false, 0L);
	} catch (TimeoutException toe) {
		throw new Error(toe); // cannot happen
	}
}

//带超时时间的 await 方法
public int await(long timeout, TimeUnit unit)
	throws InterruptedException,
		   BrokenBarrierException,
		   TimeoutException {
	return dowait(true, unit.toNanos(timeout));
}

CyclicBarrier #dowait方法源码分析:

dowait 方法是真正做到让线程在屏障前等待的方法,也是 CyclicBarrier 的核心方法。

//实现 栅栏/屏障的核心方法
private int dowait(boolean timed, long nanos)
	throws InterruptedException, BrokenBarrierException,
		   TimeoutException {
	//获取重入锁
	final ReentrantLock lock = this.lock;
	//锁定
	lock.lock();
	try {
		//获取当前代
		final Generation g = generation;

		//为ture 表示这一代是被打破了的 抛出异常
		if (g.broken)
			throw new BrokenBarrierException();
		//获取当前线程的打断状态 
		if (Thread.interrupted()) {
			//状态是被打断了的 将当前这一代设置为破坏了的 唤醒所有等待的线程  breakBarrier 源码上面分析过
			breakBarrier();
			//抛出异常
			throw new InterruptedException();
		}

		//count 还能阻塞的线程减一
		int index = --count;
		if (index == 0) {  // tripped
			//如果还能阻塞的线程为0 表示这是最后一个线程了 其他的线程都到了 栅栏/屏障前了
			//任务是否被执行的标志
			boolean ranAction = false;
			try {
				//所有线程到达栅栏/屏障前要执行的操作
				final Runnable command = barrierCommand;
				if (command != null)
					//不为空 则开始执行
					command.run();
				//任务执行标志被设置为 false
				ranAction = true;
				//进入下一代 nextGeneration 方法上面分析过源码
				nextGeneration();
				//返回
				return 0;
			} finally {
				//所有线程到达栅栏/屏障前要执行的操作发生了异常要走到这里来
				if (!ranAction)
				   //任务没有被执行   将当前这一代设置为破坏了的 唤醒所有等待的线程  breakBarrier 源码上面分析过
					breakBarrier();
			}
		}

		// loop until tripped, broken, interrupted, or timed out
		//自旋 非最后一个线程会走到这里来
		for (;;) {
			try {
				// 没有等待超时时间 false 有等待超时时间 true
				if (!timed)
					//没有等待超时时间 等待直到被唤醒
					trip.await();
				else if (nanos > 0L)
					//超时时间大于0 等待
					nanos = trip.awaitNanos(nanos);
			} catch (InterruptedException ie) {
				//中断异常
				if (g == generation && ! g.broken) {
					//当前代没有变化 且当前代没有被打断  则将当前屏障设置为已破坏 打断屏障
					breakBarrier();
					throw ie;
				} else {
					//当前代发生了变化 或者 代已经被打破了
					//代发生了变化 那表示进入了下一代  唤醒后走正常逻辑 标记一下中断过
					//代没有发生变化 但是代已经被打破了 记录下中断标记
					Thread.currentThread().interrupt();
				}
			}
			//来到这里的几种可能
			//开启了新一代 broken breakBarrier 方法会把 broken 设置为true 唤醒所有等待的线程
			//等待超时 获取到锁后 也会走到这里 
			
			//当前代的打破情况
			if (g.broken)
				//被打破 抛出异常
				throw new BrokenBarrierException();

			if (g != generation)
				//当前代已经发生了变化 表示最后一个线程到位了 触发了下一代的逻辑 因此要唤醒 条件队列中的所有线程
				//返回当前线程的 index
				return index;
			if (timed && nanos <= 0L) {
				//有等待时间 且等待时间小于等于0 那当前线程就不等了 打破规则 屏障
				breakBarrier();
				//抛出超时异常
				throw new TimeoutException();
			}
		}
	} finally {
		//解锁
		lock.unlock();
	}
}

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

我们知道 CyclicBarrier 具备重用功能,reset 方法就是实现重用功能的实现,可以看到重用功能的代码实现非常简单,就是打破屏障进入下一代即可。

CyclicBarrier 总结:

  • CyclicBarrier#await 方法会让一组线程停留在栅栏/屏障处,只有当大家都到了栅栏/屏障处,才会打开栅栏同时往下走。
  • CyclicBarrier 和 CountDownLatch、Semaphore 不一样,没有直接使用 AQS,而是基于 ReentrantLock 及 Condition 来实现的。

如有错误的地方欢迎指出纠正。

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值