首页 > 编程语言 >Java并发编程——CyclicBarrier

Java并发编程——CyclicBarrier

时间:2023-02-01 16:03:39浏览次数:64  
标签:栅栏 编程 Java int await 线程 parties CyclicBarrier

一、CyclicBarrier循环栅栏

  • CyclicBarrier是java.util.concurrent包下面的一个工具类,字面意思是可循环使用(Cyclic)的屏障(Barrier),通过它可以实现让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,所有被屏障拦截的线程才会继续执行。

  • CyclicBarrier循环栅栏和CountDownLatch很类似,都能阻塞一组线程

  • 栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。

  • CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。

上面5只小熊,准备跑到起跑线,跑到起跑线等待,相当于执行了await方法,等到所有小熊准备就绪之后,然后一起开跑。这就很好的揭示了内存屏障的作用了。

二、执行原理

CyclicBarrier是基于ReentrantLock的Condition来实现的。

如下图,栅栏中有两个关键属性:

  • parties:栅栏计数器初始值
  • count:栅栏计数器

其中CyclicBarrier的await()方法封装了对ReentrantLock条件锁的使用,主要处理流程:

  • 获取ReentrantLock锁;
  • count减1,如果此时count为0,那么唤醒等待队列中所有线程,并结束这一轮处理,重置屏障,否则进入下一步;
  • 执行condition.await方法,把当前线程丢到条件队列;
  • 当count减少到0的时候,执行condition.signalAll方法把条件队列中的所有线程节点都移动到等待队列;
  • 最后唤醒同步队列中的线程节点,线程从condition.await阻塞处醒来继续执行:获取ReentrantLock锁,用当前线程节点替换旧的头节点,最终放ReentrantLock锁,继续让线程往下执行(每个线程依次获取、锁释放锁)。如下图:

await()能够响应中断。除此之外,await还提供了带有超时的实现await(long timeout, TimeUnit unit),以及reset()方法重新开启下一轮。

三、CyclicBarrier的用法

3.1 CyclicBarrier构造方法

  • CyclicBarrier(int parties) 创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待它时,它将跳闸,并且当屏障跳闸时不执行预定义的动作。
  • CyclicBarrier(int parties, Runnable barrierAction) 创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。

3.2 CyclicBarrier方法

  • int await():等待所有 parties已经在这个障碍上调用了 await 。
  • int await(long timeout, TimeUnit unit):等待所有 parties已经在此屏障上调用 await ,或指定的等待时间过去。
  • int getNumberWaiting():返回目前正在等待障碍的各方的数量。
  • int getParties():返回旅行这个障碍所需的parties数量。
  • boolean isBroken():查询这个障碍是否处于破碎状态。
  • void reset():将屏障重置为初始状态。

3.3 CyclicBarrier使用

/**
 * @Description: 演示CyclicBarrier的使用
 */
public class CyclicBarrierDemo {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
            System.out.println("所有人都到场了,大家统一出发");
        });

        for (int i = 0; i < 10; i++) {
            final int id = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+",id:"+id+"现前往集合地点");
                try {
                    Thread.sleep(new Random().nextInt(10000));
                    System.out.println(Thread.currentThread().getName()+"到了集合地点,开始等待其他人到达");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName()+"出发了");
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3.4 CyclicBarrier和CountDownLatch的区别

  • 作用不同:CyclicBarrier要等固定数量的线程都到达了栅栏位置才能继续执行,而CountDownLatch只需要等待数字到0,也就是说,CountDownLatch用于事件,CyclicBarrier是用于线程的
  • 可重用性不同:CountDownLatch在倒数到0并触发门闩打开后,就不能再次使用了,除非新建实例;而CyclicBarrier可以重复使用
  • CountDownLatch是减计数方式,而CyclicBarrier是加计数方式。
  • CountDownLatch计数为0无法重置,而CyclicBarrier计数达到初始值,则可以重置。

四、源码

CyclicBarrier是由ReentrantLock可重入锁和Condition共同实现的。

4.1 CyclicBarrier构造方法

public class 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();
	
	//计数器
	private int count;
	
	//静态内部类Generation
    private static class Generation {
        boolean broken = false;
    }
	
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
		// parties表示“必须同时到达barrier的线程个数”。
        this.parties = parties;
		// count表示“处在等待状态的线程个数”。
        this.count = parties;
		// barrierCommand表示“parties个线程到达barrier时,会执行的动作”。
        this.barrierCommand = barrierAction;
    }
}

4.2 await源码分析

public class CyclicBarrier {

    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
}
public class CyclicBarrier {

    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
		// 获取“独占锁(lock)”
        lock.lock();
        try {
			// 保存“当前的generation”
            final Generation g = generation;

			// 若“当前generation已损坏”,则抛出异常。
            if (g.broken)
                throw new BrokenBarrierException();

			// 如果当前线程被中断,则通过breakBarrier()终止CyclicBarrier,唤醒CyclicBarrier中所有等待线程。
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

			// 将“count计数器”-1
            int index = --count;
			// 如果index=0,则意味着“有parties个线程到达barrier”。
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
					// 如果barrierCommand不为null,则执行该动作。
                    if (command != null)
                        command.run();
                    ranAction = true;
					// 唤醒所有等待线程,并更新generation。
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 当前线程一直循环,直到“有parties个线程到达barrier” 或 “当前线程被中断” 或 “超时”这3者之一发生,
			// 当前线程才继续执行。
            for (;;) {
                try {
					// 如果不是“超时等待”,则调用await()进行等待;否则,调用awaitNanos()进行等待。
                    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();
                    }
                }
				// 如果“当前generation已经损坏”,则抛出异常。
                if (g.broken)
                    throw new BrokenBarrierException();
				// 如果“generation已经换代”,则返回index。
                if (g != generation)
                    return index;
				// 如果是“超时等待”,并且时间已到,则通过breakBarrier()终止CyclicBarrier,
				// 唤醒CyclicBarrier中所有等待线程,并抛出TimeoutException异常
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
			// 释放“独占锁(lock)”
            lock.unlock();
        }
    }
}

参考: https://www.itzhai.com/articles/graphical-several-fun-concurrent-helper-classes.html

https://www.cnblogs.com/200911/p/6060195.html

标签:栅栏,编程,Java,int,await,线程,parties,CyclicBarrier
From: https://blog.51cto.com/u_14014612/6031634

相关文章

  • Java并发编程——Semaphore
    一、SemaphoreSemaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。S......
  • Java并发编程——并发包中锁的AQS通用实现
    一、包结构介绍我们查看下java.util.concurrent.locks包下面,发现主要包含如下类:可以发现ReentrantLock和ReentrantReadWriteLock都是AbstractQueueSynchronizer类。我们......
  • Java并发编程——ArrayBlockingQueue
    一、阻塞队列BlockingQueue在java.util.concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速......
  • Java并发编程——LinkedBlockingQueue
    一、阻塞队列BlockingQueue在java.util.concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速......
  • java/Android获取单个文件的MD5值,解决首位0被省略问题,解决超大文件问题,cmd命令行查看
    code来源:https://betheme.net/news/txtlist_i67135v.html?action=onClickcmd命令行查看文件md5码:certutil-hashfilea.txtmd5,不加后面的md5,查看的默认是sha1码。packag......
  • 【Java】自定义mybatis
    处理sqlin多条件搜索时单引号双引号问题StringBuilderpidNoZeroIds=newStringBuilder();IntegernumTmp=0;for(ShequLsDatingTypepidNoZero:pidNoZeroLis......
  • Java线程池中的execute和submit
    一、概述execute和submit都是线程池中执行任务的方法。execute是Executor接口中的方法publicinterfaceExecutor{voidexecute(Runnablecommand);}submit是......
  • Java多线程:Future和FutureTask
    一、FutureFuture是一个接口,所有方法如下:上源码:packagejava.util.concurrent;publicinterfaceFuture<V>{booleancancel(booleanmayInterruptIfRunning);......
  • 使用java python 实现 QI-页面排序-骑马钉
    链接:http://www.cnprint.org/bbs/thread/77/339531/......
  • Spring开启@Async异步方法(javaconfig配置)
    在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。应用场景:某些耗时较长的......