了解Semaphore和CyclicBarrier

139 阅读3分钟

Semaphore 功能演示

semaphore也就是我们常说的信号灯,semaphore可以控制同时访问的线程个数,通过 acquire 获取一个许可,如果没有就等待,通过release释放一个许可。有点类似限流的作用。叫信号灯的原因也和他的用处有关,比如某商场就5个停车位,每个停车位只能停一辆车,如果这个时候来了10辆车,必须要等前面有空的车位才能进入。

  • 代码演示

  • Semaphore比较常见的就是用来做限流操作了。

简单了解一下Semaphore源码

从 Semaphore 的功能来看,我们基本能猜测到它的底层实现一定是基于AQS的共享锁,因为需要实现多个线程共享一个领排池。

创建 Semaphore 实例的时候,需要一个参数 permits,这个基本上可以确定是设置给 AQS 的 state 的,然后每个线程调用 acquire 的时候,执行 state = state - 1, release 的时候执行 state = state + 1,当然,acquire 的时候,如果 state = 0,说明没有资源了,需要等待其他线程 release。

Semaphore 分公平策略和非公平策略

 		 if (hasQueuedPredecessors())
                    return -1;

区别仅仅在于,公平锁会先判断链表中是否已经存在了节点,如果存在了则直接进入链表等待,而非公平锁直接判断 state 的值是否< 0 (进行了一次插队操作)

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //是否被中断
        if (Thread.interrupted())
            throw new InterruptedException();
        //对 state 的值- 1,如果结果 >= 0表示没有超过限制的线程数,直接放行,如果超过了则添加到一个双向链表中
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
  • release() 方法
    public final boolean releaseShared(int arg) {
    	//对 state 进行了 +1 操作
        if (tryReleaseShared(arg)) {
        	//唤醒头节点的线程
            doReleaseShared();
            return true;
        }
        return false;
    }

由于大部分的代码和CountDownLatch的是完全一样,都是基于共享锁的实现,所以也就没必要再花时间来分析了。

CyclicBarrier 功能演示

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续工作。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 当前线程已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier(int parties,Runable runa) 当存在需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用CyclicBarrier

  • 代码演示

  • 我们创建了 CyclicBarrier 对象并且设置了阻塞的线程数和子线程,当我们的三个测试线程都被阻塞后,执行子线程,并且被阻塞的线程数达到了最大阻塞个数,全部放行。

  • 注意点

  1. 对于指定计数值parties,若由于某种原因,没有足够的线程调用CyclicBarrier的await,则所有调用await的线程都会被阻塞
  2. 同样的CyclicBarrier也可以调用await(timeout, unit),设置超时时间,在设定时间内,如果没有足够线程到达,则解除阻塞状态,继续工作;
  3. 通过 reset 重置计数,会使得进入 await 的线程出现 BrokenBarrierException;
  4. 如果采用是 CyclicBarrier(int parties, Runnable barrierAction) 构造方法,执行 barrierAction 操作的是最后一个到达的线程
  5. CyclicBarrier 可以有不止一个栅栏,因为它的栅栏(Barrier)可以重复使用(Cyclic)
  6. CyclicBarrier 相比 CountDownLatch 来说要简单很多,主要的区别就是栅栏可以重复使用,源码实现是基于ReentrantLock 和 Condition 的组合使用。
  • 举个例子:司令下达命令,要求10个士兵一起去执行任务。这时,就会要求10个士兵先集合报道,接着,一起去执行任务。当任务执行完毕时,司令就会宣布任务执行完成。然后下一次召集的时候,还得等着10个士兵集合了才能出发。