JUC相关连载四---AQS常见Tools之 Semaphore、CountDownLatch、CyclicBarrier(Java劝退师)

       这些工具。。其实都是基于AQS的使用封装,其原理就不多讲了,AQS可以看下之前的文章。这里主要讲下几个方法和使用。

一、Semaphore

        Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。可以简单的说是一个限流。就像停车场里的剩余车位,来一个少一个,车位满了就要等待。或者说是饭店里的餐位,一共一百个,满员后再来客人就要排队等待。也可以想成是连接池,拿走一个连接就少一个。

        1、构造方法:

        public Semaphore(int permits)
        public Semaphore(int permits, boolean fair)
       permits 表示许可线程的数量
       fair 表示公平性,如果这个设为 true 的话,下次获取资源执行的线程会是等待最久的那个线程

        2、常用方法:

        acquire()  获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。

       acquire(int permits)  获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
    
       acquireUninterruptibly() 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
    
       tryAcquire()尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。

       tryAcquire(long timeout, TimeUnit unit) 尝试获得令牌,在超时时间内自旋尝试获取,直到尝试获取成功或超时返回,不阻塞线程。

       release()释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。

       hasQueuedThreads()等待队列里是否还存在等待线程。

       getQueueLength()获取等待队列里阻塞的线程数。

       drainPermits()清空令牌把可用令牌数置为0,返回清空令牌的数量。

       availablePermits()返回可用的令牌数量。

        使用举例:

        

        图上获取了两个资源。线程中只有在前两个线程有资源释放的时候,其他线程才有机会获取。

        实现原理:其实也是AQS的简单的运用(AQS前面已经详细说明)。

        1、创建一个有两个资源的资源池,其实就是AQS的state设置为2。

         2、获取一个资源,state-1. 当state < 0表示资源不足,这个时候会创建一个Node节点加入到CHL,然后挂起当前线程。

         3、其他线程释放资源后,state+1,唤醒队列中的节点后,state-1.

二、CountDownLatch

        来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

         场景:

         1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown()(通常在finally语句里执行),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。(主线程等待其他线程执行完)

      2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。(主线程宣布其他线程同时执行)

      主要方法:()

     CountDownLatch.countDown()   //计数器-1
     CountDownLatch.await();    //计数器在归0之前,处于阻塞状态

     实现原理(理解AQS的大概也能猜到步骤)

    1、初始化CountDownLatch的时候将AQS的state值赋值为传进来 CountDownLatch的计数器的数量。

     2、调用 countDown 将计数器-1. (若果减1后state为0,则唤醒CHL队列中的第一个线程。第一个线程则继续唤醒下一个线程)

    3、调用 await 方法。查看state是否为0.如果为0,则唤醒CHL队列中的第一个线程。第一个线程则继续唤醒下一个线程),如果不为0,创建Node节点加入到CHL队列。

     特点(有人说是缺点,看你怎么用它了):初始化后只能使用一次。就像田径比赛。所有人调用countDown方法后在那等着,裁判调用await方法后,所有人起头并。这个时候只能计数一次并没有       任何问题。只能说是看场景需要了。

三、CyclicBarrier  

       栅栏,也可以说是一个屏障,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。上亿人都知道的场景:王者荣耀 5v5。等所有队友都确认到齐后才会开始一组新的游戏(所有参与者可以开始发挥自己的风骚操作了)。只要有一个人没到齐,都不能开始。(所有线程到达一个屏障点之后,才继续干各自的事情)

       基于条件队列(条件队列下回说道)和lock实现的,时间是对count进行的计数操作。

       

      主要方法:cyclicBarrier.await();  breakBarrier  等待方法和打破栅栏方法

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();
            }
            
			//当调用await方法,将count方法-1.
            int index = --count;
			 //当count等于0证明当前线程就是最后一个到达的现场,就不用再等待了
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
					//到这里证明人已到期,执行初始化时候的既定逻辑(barrierCommand 是初始化时指定到齐后要执行的逻辑)
                    final Runnable command = barrierCommand;
                    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();
        }
    }

       

 private void breakBarrier() {
        //打破栅栏,或者说是本次计数结束
        generation.broken = true;

        //重置count,这里也是说明栅栏能复用的地方
        count = parties;

       //唤起所有等待线程开跑
        trip.signalAll();
    }

CountDownLatch  和 CyclicBarrier 的区别:(引用自网络总结)

1、CountDownLatch 的线程分为两类:一个是等待者,另一个是计数者;CyclicBarrier 参与的线程既是等待者,也是计数者。
2、CountDownLatch 一次性消费品,CountDownLatch 可以复用。
3、CountDownLatch 的计数值与线程没有必然联系,CyclicBarrier 的初始计数值与线程个数一致。
4、CountDownLatch 基于AQS实现,CyclicBarrier 基于ReentrantLock&Condition实现(再内部其实
ConditionObject也是基于AQS啦)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值