CyclicBarrier

源码分析

回环删栏,其作用是让一组线程等待至某个状态的时候之后才可以通知执行。且当所有线程都释放之后,CyclicBarrier可以被重用。我们把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。

这个类没有直接继承AQS,而是通过组合的方式来实现的,利用了ReentrantLock 和 Condition。

创建CyclicBarrier的时候会设定一个count,仍在等待的数量

每个线程调用await的时候,表示自己已经到达删栏了,判断屏障是否被破坏, 线程是否被中断,判断减少之后的count是否等于0.如果的呢过0,表示所有线程都进行进入,执行跳闸时的运行方法。如果不等于0,则会通过Condition的await进行等待。

也可以重置一个删栏,唤醒所有等待线程,恢复正在等待进去屏障的线程数,进行下一个版本

类继承关系

CyclicBarrier没有显示继承哪个父类或者实现哪个父接口, 所有AQS和重入锁不是通过继承实现的,而是通过组合实现的。

//CyclicBarrier类存在一个内部类Generation,每一次使用的CycBarrier可以当成Generation的实例
public class CyclicBarrier {
    private static class Generation {
        Generation() {}                 // prevent access constructor creation
        boolean broken;                 // initially false   初始=false 用来表示当前屏障是否被损坏
    }
} 

类属性

public class CyclicBarrier {

    // 可重入锁
    private final ReentrantLock lock = new ReentrantLock();
    // 条件队列
    private final Condition trip = lock.newCondition();
    // 参与的线程数量
    private final int parties;
    // 由最后一个进入 barrier 的线程执行的操作
    private final Runnable barrierCommand;
    // 当前代
    private Generation generation = new Generation();
    // 正在等待进入屏障的线程数量
    private int count;
}

该属性有一个为ReentrantLock对象,有一个为Condition对象,而Condition对象又是基于AQS的,所以底层还是由AQS提供支持。

构造函数

//该构造函数可以指定关联该CyclicBarrier的线程数量,并且可以指定在所有线程都进入屏障后的执行动作,该执行动作由最后一个进行屏障的线程执行。
public CyclicBarrier(int parties, Runnable barrierAction) {
    // 参与的线程数量小于等于0,抛出异常
    if (parties <= 0) throw new IllegalArgumentException();
    // 设置parties
    this.parties = parties;
    // 设置count
    this.count = parties;
    // 设置barrierCommand
    this.barrierCommand = barrierAction;
}
//该构造函数仅仅执行了关联该CyclicBarrier的线程数量,没有设置执行动作。
public CyclicBarrier(int parties) {
    // 调用含有两个参数的构造函数
    this(parties, null);
}

核心方法分析

dowait

CyclicBarrier类对外提供的await在底层都是调用了doawait

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();
        }
        
        // 减少正在等待进入屏障的线程数量
        int index = --count;
        if (index == 0) {  // 正在等待进入屏障的线程数量为0,所有线程都已经进入
            // 运行的动作标识
            boolean ranAction = false;
            try {
                // 保存运行动作
                final Runnable command = barrierCommand;
                if (command != null) // 动作不为空
                    // 运行
                    command.run();
                // 设置ranAction状态
                ranAction = true;
                // 进入下一代
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction) // 没有运行的动作
                    // 损坏当前屏障
                    breakBarrier();
            }
        }

        // 无限循环
        for (;;) {
            try {
                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();
                }
            }

            if (g.broken) // 屏障被损坏,抛出异常
                throw new BrokenBarrierException();

            if (g != generation) // 不等于当前代
                // 返回索引
                return index;

            if (timed && nanos <= 0L) { // 设置了等待时间,并且等待时间小于0
                // 损坏屏障
                breakBarrier();
                // 抛出异常
                throw new TimeoutException();
            }
        }
    } finally {
        // 释放锁
        lock.unlock();
    }
}

dowait方法的逻辑会进行一系列的判断,大致流程如下:
在这里插入图片描述

nextGeneration

在所有线程进入屏障后会被调用,即生成下一个版本,所有线程又可以重新进入到屏障中。重置删栏,也是锁不断重用的基础

private void nextGeneration() {
    // 唤醒所有线程
    trip.signalAll();
    // 恢复正在等待进入屏障的线程数量
    count = parties;
    // 新生一代
    generation = new Generation();
}

//调用AQS的signalAll方法,即唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程
//判断头节点是否为空,即条件队列是否为空,然后会调用doSignalAll函数
public final void signalAll() {
    if (!isHeldExclusively()) // 不被当前线程独占,抛出异常
        throw new IllegalMonitorStateException();
    // 保存condition队列头节点
    Node first = firstWaiter;
    if (first != null) // 头节点不为空
        // 唤醒所有等待线程
        doSignalAll(first);
}

//会依次将条件队列中的节点转移到同步队列中,会调用到transferForSignal
private void doSignalAll(Node first) {
    // condition队列的头节点尾结点都设置为空
    lastWaiter = firstWaiter = null;
    // 循环
    do {
        // 获取first结点的nextWaiter域结点
        Node next = first.nextWaiter;
        // 设置first结点的nextWaiter域为空
        first.nextWaiter = null;
        // 将first结点从condition队列转移到sync队列
        transferForSignal(first);
        // 重新设置first
        first = next;
    } while (first != null);
}

//就是将处于条件队列中的节点转移到同步队列中,并设置结点的状态信息,其中会调用到enq函数
final boolean transferForSignal(Node node) {

    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

//完成了结点插入同步队列的过程
private Node enq(final Node node) {
    for (;;) { // 无限循环,确保结点能够成功入队列
        // 保存尾结点
        Node t = tail;
        if (t == null) { // 尾结点为空,即还没被初始化
            if (compareAndSetHead(new Node())) // 头节点为空,并设置头节点为新生成的结点
                tail = head; // 头节点与尾结点都指向同一个新生结点
        } else { // 尾结点不为空,即已经被初始化过
            // 将node结点的prev域连接到尾结点
            node.prev = t; 
            if (compareAndSetTail(t, node)) { // 比较结点t是否为尾结点,若是则将尾结点设置为node
                // 设置尾结点的next域为node
                t.next = node; 
                return t; // 返回尾结点
            }
        }
    }
}

newGeneration函数的主要方法的调用如下:
在这里插入图片描述

breakBarrier

损坏当前屏障,会唤醒所有在屏障中的线程

private void breakBarrier() {
    // 设置状态
    generation.broken = true;
    // 恢复正在等待进入屏障的线程数量
    count = parties;
    // 唤醒所有线程
    trip.signalAll();
}

示例分析

示例1

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

 class MyThread extends Thread {
    private CyclicBarrier cb;
    public MyThread(String name, CyclicBarrier cb) {
        super(name);
        this.cb = cb;
    }
    
    public void run() {
        System.out.println(Thread.currentThread().getName() + " going to await");
        try {
            cb.await();
            System.out.println(Thread.currentThread().getName() + " continue");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * CyclicBarrier类似于CountDownLatch也是个计数器,
 * 不同的是CyclicBarrier数的是调用了CyclicBarrier.await()进入等待的线程数,
 * 当线程数达到了CyclicBarrier初始时规定的数目时,所有进入等待状态的线程被唤醒并继续。
 * CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。
 * CyclicBarrier初始时还可带一个Runnable的参数,
 * 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        CyclicBarrier cb = new CyclicBarrier(3, new Thread("barrierAction") {
            public void run() {
                //当所有线程都到了屏障,执行该方法
                System.out.println(Thread.currentThread().getName() + " barrier action");
                
            }
        });
        MyThread t1 = new MyThread("t1", cb);
        MyThread t2 = new MyThread("t2", cb);
        t1.start();
        t2.start();
        System.out.println(Thread.currentThread().getName() + " going to await");
        cb.await();
        System.out.println(Thread.currentThread().getName() + " continue");

    }
}
//console
t1 going to await
main going to await
t2 going to await
t2 barrier action
t2 continue
t1 continue
main continue

根据结果可知,存在如下的调用时序。

在这里插入图片描述

由上图可知,假设t1线程的cb.await是在main线程的cb.barrierAction动作是由最后一个进入屏障的线程执行的。根据时序图,进一步分析出其内部工作流程。

main(主)线程执行cb.await操作,主要调用的函数如下。
在这里插入图片描述

由于ReentrantLock的默认采用非公平策略,所以在dowait函数中调用的是ReentrantLock.NonfairSync的lock函数,由于此时AQS的状态是0,表示还没有被任何线程占用,故main线程可以占用,之后在dowait中会调用trip.await函数,最终的结果是条件队列中存放了一个包含main线程的结点,并且被禁止运行了,同时,main线程所拥有的资源也被释放了,可以供其他线程获取。

t1线程执行cb.await操作,其中假设t1线程的lock.lock操作在main线程释放了资源之后,则其主要调用的函数如下。
在这里插入图片描述

可以看到,之后condition queue(条件队列)里面有两个节点,包含t1线程的结点插入在队列的尾部,并且t1线程也被禁止了,因为执行了park操作,此时两个线程都被禁止了。

t2线程执行cb.await操作,其中假设t2线程的lock.lock操作在t1线程释放了资源之后,则其主要调用的函数如下。

在这里插入图片描述

由上图可知,在t2线程执行await操作后,会直接执行command.run方法,不是重新开启一个线程,而是最后进入屏障的线程执行。同时,会将Condition queue中的所有节点都转移到Sync queue中,并且最后main线程会被unpark,可以继续运行。main线程获取cpu资源,继续运行。

main线程获取cpu资源,继续运行,下图给出了主要的方法调用:

在这里插入图片描述

其中,由于main线程是在AQS.CO的wait中被park的,所以恢复时,会继续在该方法中运行。运行过后,t1线程被unpark,它获得cpu资源可以继续运行。

t1线程获取cpu资源,继续运行,下图给出了主要的方法调用。

在这里插入图片描述

其中,由于t1线程是在AQS.CO的wait方法中被park,所以恢复时,会继续在该方法中运行。运行过后,Sync queue中保持着一个空节点。头节点与尾节点均指向它。

注意: 在线程await过程中中断线程会抛出异常,所有进入屏障的线程都将被释放。

示例2

CyclicBarrier的重用

//模拟斗地主
public class CyclicBarrierUseCase1 {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3);
        ExecutorService service = Executors.newCachedThreadPool();
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            int num = i;
            service.submit(() -> {
                try {
                    System.out.println(num + " 准备去棋牌馆");
                    TimeUnit.SECONDS.sleep(random.nextInt(5));
                    System.out.println(num + " 到达");
                    barrier.await();
                    System.out.println(num + " 斗地主");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

//console
0 准备去棋牌馆
1 准备去棋牌馆
2 准备去棋牌馆
3 准备去棋牌馆
4 准备去棋牌馆
5 准备去棋牌馆
2 到达
4 到达
5 到达
5 斗地主
2 斗地主
4 斗地主
0 到达
1 到达
3 到达
3 斗地主
0 斗地主
1 斗地主

和CountDonwLatch对比

  • CountDownLatch减计数,CyclicBarrier加计数。
  • CountDownLatch是一次性的,CyclicBarrier可以重用。
  • CountDownLatch和CyclicBarrier都有让多个线程等待同步然后再开始下一步动作的意思,但是CountDownLatch的下一步的动作实施者是主线程,具有不可重复性;而CyclicBarrier的下一步动作实施者还是“其他线程”本身,具有往复多次实施动作的特点。
  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值