ReentrantLock、CountDownLatch 、Semaphore三者底层都是AbstractQueuedSynchronizer,逻辑都是先获取通行许可,成功了执行接下来的代码,失败了挂起;另外就是要在合适的时候唤醒其他线程。
对照上面的流程
ReentrantLock获取通行许可是lock---acquire---tryAcquire方法用cas把state从0变到1,唤醒其他线程是unlock---release---tryRelease方法中,同时把state从1变到0(暂时忽略可重入的情况)。
CountDownLatch 获取通行许可是await---acquireSharedInterruptibly---tryAcquireShared 检测state是否为0,唤醒其他线程是countDown---releaseShared---tryReleaseShared判断state是否大于0,是的话state减一,如果减一之后恰好为0,则唤醒其他线程。
Semaphore获取资源是acquire---acquireSharedInterruptibly---tryAcquireShared 检测state是否大于0,是的话减去1并获取通行许可;唤醒线程是在release---releaseShared---tryReleaseShared把state加1,并唤醒其他线程。
CyclicBarrier:和上面说的三者不同,用的是ReentrantLock的await和singalAll,获取通行许可是用await---dowait---用ReentrantLock加锁并让count减一,如果count为0,则获取许可,同时执行回调方法,并唤醒等待队列,而且会开启下一轮等待,如果不为0,用condition.await挂起到等待队列;所以CyclicBarrier是把获取许可之后会同时唤醒其他线程,可以循环的await多次。
再说说join(): Thread类的同步方法,假设代码:Thread a = new Thread(); a.start(); a.join(); 这段代码在线程b中执行,b执行到a.join的时候,进入同步锁,无限循环中,判断a是否存活,如果a存活,调用a.wait()挂起线程b。当a线程执行完或者异常退出的时候,JVM会调用a.notifyAll方法唤醒b,b被唤醒后,此时a已经退出,b向下执行完a.join方法。
ps:想到这里,突然发现既然CountDownLatch是一次性的,那么RocketMQ源码在consumer rebalance的时候,是一个countDownLatch在循环的等待wakeUp也就是重新负载均衡的信号,这是为什么呢?进去一看,原来是rocketMQ重新写了一个,增加了reset也就是重新设置state的方法,但是这个state按道理来讲是不能随便重新设置的,有线程安全问题的,重写的countDownLatch用了hasNotified这个Atomic的变量进行了隔离来解决这个问题。