JUC学习(三):线程同步之CyclicBarrier

目录

一.简介

二.构造方法

三.await方法


一.简介

在 JUC学习(二):线程同步之CountDownLatch 中,介绍了CDL,它可以实现线程同步,但是也有一个不足:无法重用。一旦计数器清零,CDL对象就无法再使用。如果一个任务中,需要多次进行线程同步,那么就要设置多个CDL,不但浪费资源,而且对象多了也容易搞混,不利于维护。CyclicBarrier就能够实现重用。不同的是,CDL是一组线程等待另一组线程,CyclicBarrier是一组线程间相互等待。

下面是一段示例代码:

import java.util.Random;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest {
    private static CyclicBarrier barrier = new CyclicBarrier(5, () -> System.out.println("线程们都准备好了!"));

    public static void testMethod(){
        while(true){
            try{
                Thread.sleep(new Random().nextInt(10)*1000);
                System.out.println(Thread.currentThread().getName()+"准备好了!");
                barrier.await();
            }catch (Exception e) {

            }
        }
    }

    public static void main(String args[]) {
        Thread[] threads = new Thread[5];
        for(int i=0;i<5;i++){
            threads[i] = new Thread(()->testMethod(),"Thread-"+i);
        }
        for(int i=0;i<5;i++){
            threads[i].start();
        }
    }
}

运行结果类似于:

Thread-0准备好了!
Thread-4准备好了!
Thread-3准备好了!
Thread-1准备好了!
Thread-2准备好了!
线程们都准备好了!
Thread-4准备好了!
Thread-2准备好了!
Thread-1准备好了!
Thread-3准备好了!
Thread-0准备好了!
线程们都准备好了!
Thread-0准备好了!
Thread-1准备好了!
Thread-2准备好了!
Thread-3准备好了!
Thread-4准备好了!
线程们都准备好了!

可以无限运行下去。可以看到,barrier可以重复使用。

二.构造方法

和CDL不同,CyclicBarrier构造方法可以额外接受一个Runnable对象,作为线程全部就绪后进行的操作:

    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

    public CyclicBarrier(int parties) {
        this(parties, null);
    }

三.await方法

await方法实际调用了dowait方法,下面分段解说该方法:

首先,使用ReentrantLock加锁,并进行一些状态检验:

        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();
            }
            ...
        } finally {
            lock.unlock();
        }

Generation是一个静态内部类,只有一个成员变量broken。它是用来标记每一代barrier的状态的。

接下来是判断barrier是否已经被打破的代码:

            int index = --count;
            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();
                }
            }

每有一个线程执行await,都会将计数器值减1,假如计数器清零,就会执行预设的任务(如果有的话),并开始下一轮同步。

如果barrier还没有被打破,就先自旋等待:

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

可以看到,退出等待的方法有四个:1)超时(如果设置了时限的话);2)线程中断;3)barrier被打破(即count为0);4)当前世代的barrier已结束(实际上就是执行了reset方法)

reset的一个例子是,假如CyclicBarrier的count值大于线程总数,可以用该方法手动结束同步状态:

public class CyclicBarrierTest {
    private static CyclicBarrier barrier = new CyclicBarrier(6, () -> System.out.println("线程们都准备好了!"));

    public static void testMethod(){
        ...
    }

    public static void main(String args[]) throws InterruptedException {
        Thread[] threads = new Thread[5];

        for(int i=0;i<5;i++){
            threads[i] = new Thread(()->testMethod(),"Thread-"+i);
        }
        for(int i=0;i<5;i++){
            threads[i].start();
        }
        Thread.sleep(10000);
        barrier.reset();
    }
}

testMethod方法的代码与示例代码相同。上面的代码中,将barrier的计数器值改为6,并设置了main线程sleep十秒钟后执行barrier的reset方法,结果如下:

Thread-0准备好了!
Thread-4准备好了!
Thread-3准备好了!
Thread-1准备好了!
Thread-2准备好了!
//上面是reset执行之前的输出
Thread-1准备好了!
Thread-2准备好了!
Thread-4准备好了!
Thread-0准备好了!
Thread-3准备好了!

为了方便观察效果,我加了一行“注释”,实际输出没有这行。可以看到,reset之后,第一轮同步中断,没有执行我们预设的任务;紧接着开始了第二轮同步,不过由于线程数不足以打破barrier,使得第二轮同步永远地阻塞了下去。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值