CyclicBarrier、CountDownLatch以及Semaphore是Java并发包中几个常用的并发组件,这几个组件特点是功能相识很容易混淆。首先我们分别介绍这几个组件的功能然后再通过实例分析和源码分析其中设计原理。
CyclicBarrier
主要功能:
CyclicBarrier的主要功能是使1~(N-1)个线程达到某个屏障后阻塞,直到第N个线程到达该屏障后才会被打开,这是所有的线程才能继续执行下去,CyclicBarrier同时支持一个Runable对象,当所有线程到达该屏障时执行该Runable对象。
用例:
输出:
Thread-0达到屏障 Thread-2达到屏障 Thread-1达到屏障 Thread-1离开屏障 Thread-2离开屏障 Thread-0离开屏障
可以看到三个线程同时达到屏障后所有线程才开始离开屏障继续运行。下面我们将分析其设计原理。
设计原理
CyclicBarrier调用await()方法是线程等待,await()方法源码如下:
其内部调用的是doWait()方法,await()还有一个带超时的重载方法,功能类似。doWait()方法代码如下:
Generation是一个静态内部类,表明CyclicBarrier的代,表明每个CyclicBarrier执行的实例,如果当前CyclicBarrier正常执行完将会重置代,否则将会破坏代。
breakBarrier方法会破坏屏障,可以看到起设置了代为破坏状态同时调用Condition的signalAll方法唤醒所有在等待的线程。
nextGeneration主要作用为重置下一代,内部也会唤醒正在等待的线程同时将屏障数量复位方便下一次使用。
CountDownLatch
CountDownLatch的主要功能是实现几个计数器,使N个现场执行完成后当前线程才会继续执行下去。比如我们希望将一个事件分成多个线程去执行,执行完后进行汇总这种情景就可以使用CountDownLatch。
用例
输出:
Thread-0执行完毕 Thread-1执行完毕 Thread-2执行完毕 Thread-3执行完毕 Thread-4执行完毕 主线程执行
原理分析
构造方法
CountDownLatch构造函数中调用了Sync构造方法,Sync继承了AQS内容如下:
设置了state,这个东西再熟悉不过了,在可重入锁中表示是否获取到锁的标志位。
我们首先看await方法,
实际调用的是AQS的acquireSharedInterruptibly方法,从名字可以看出采用的是共享模式。
tryAcquireShared代码在Sync中被实现如下:
如果state等于0返回1,否则返回-1;state等于0说明没有对象在同步器中,线程可以继续执行下去,否则进入doAcquireSharedInterruptibly方法中,doAcquireSharedInterruptibly方法如下:
这段代码就是共享模式获取节点,获取不到就进入队列中休眠,这个跟读写锁一样,知道state等于0后被唤醒。
countDown方法如下:
调用的是Sync的releaseShared方法,
countDown方法的主要功能就是通过CAS方法减少state的值,减少成功后唤醒队列中的节点。唤醒主节点成功后doAcquireSharedInterruptibly中方法会继续执行接着判断state是否等于0,不等与继续休眠否则继续执行线程。
Semaphore可以控制访问线程的数量。
用例:
原理分析
首先看构造方法,
默认构造方法采用非公平模式。
acquire方法如下:
很熟悉默认采用的是共享模式获取节点信息,跟读锁类似。
release方法如下:
acquireSharedInterruptibly方法如下:
tryReleaseShared方法如下:
总结
CyclicBarrier主要用于N个线程之间互斥,当且仅当N个线程都执行到屏障处所有线程才能继续执行下去,CyclicBarrier可以被重复使用,CyclicBarrier通过可冲入锁+AQS+Condition实现,CyclicBarrier调用await方法获取可重入锁同时减少state的值,state==0时唤醒所有正在等待的线程,否则线程处于等待状态,线程间的通信主要通过Condition机制来实现。
。
CountDownLatch主要用于某个线程等待N个线程执行完后等待的线程接着继续执行下去,不能够重复执行,CountDownLatch通过设施AQS state值来实现,每次调用counDown方法后都去唤醒正在等待的线程,等待的线程判断state是否等于0,等于0就继续执行。
Semaphore用于控制访问线程的数量,Semaphore通过设置AQS state值来实现,调用require方法后cas减少state的值,如果state值为负数说明有更多线程正在访问代码块,这是后需要把这些线程休眠,调用release方法后重新增加state值,重新增加state值后去唤醒正在等待的线程。