三个同步工具类需要学习底层AQS,各位学了再来会很容易理解
CountDownLatch闭锁
CountDownLatch中定义了加了final的静态内部类,其继承了AQS,用于实现同步锁机制。其中它重写了获取锁了释放锁。
机制在于获取锁会根据state为0为标准判定是否获取成功,releas每次减少一个数值。
运行逻辑在于设置一把有State数量层锁的大门,每个线程执行await时都会因为门被锁住而加入同步队列,然后设置前节点的waitstate为-1进行堵塞,每个线程执行到countdown时会解一把锁,解锁是会唤醒一个线程,它会再获取锁,但是state还是不够,继续加入同步队列,进行阻塞,也就是将state减少一直到state为0时.会唤醒等待队列节点,这时候他就能跑了。
CyclicBarrier
它的底层比较简单就是基于ReentrantLock和Condition的操作,
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//获取锁
lock.lock();
try {
每个线程都有一个个Generation,默认broken为true
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//重点减少一次
//线程到一个减一次
int index = --count;
//减到最后是执行,不是最后一个跳到下面加入条件队列并加锁。
if (index == 0) { // tripped
boolean ranAction = false;
try {
//执行自定义的runable方法
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)
将线程加入条件队列condition
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();
}
}
源码分析,用上面的示例总结一下CyclicBarrier的await方法实现,假设线程thread1和线程thread2都执行到CyclicBarrier的await(),都进入dowait(boolean timed, long nanos),thread1先获取到独占锁,执行到–count的时,index等于1,所以进入下面的for循环,接着执行trip.await(),进入await()方法,执行Node node = addConditionWaiter()将当前线程构造成Node节点并加入到Condition等待队列中,然后释放获取到的独占锁,当前线程进入阻塞状态;此时,线程thread2可以获取独占锁,继续执行–count,index等于0,所以先执行command.run(),输出myThread,然后执行nextGeneration(),nextGeneration()中trip.signalAll()只是将Condition等待队列中的Node节点按之前顺序都转移到了AQS同步队列中,这里也就是将thread1对应的Node节点转移到了AQS同步队列中,thread2执行完nextGeneration(),返回return 0之前,细看代码还需要执行lock.unlock(),这里会执行到ReentrantLock的unlock()方法,最终执行到AQS的unparkSuccessor(Node node)方法,从AQS同步队列中的头结点开始释放节点,唤醒节点对应的线程,即thread1恢复执行。
如果有三个线程thread1、thread2和thread3,假设线程执行顺序是thread1、thread2、thread3,那么thread1、thread2对应的Node节点会被加入到Condition等待队列中,当thread3执行的时候,会将thread1、thread2对应的Node节点按thread1、thread2顺序转移到AQS同步队列中,thread3执行lock.unlock()的时候,会先唤醒thread1,thread1恢复继续执行,thread1执行到lock.unlock()的时候会唤醒thread2恢复执行。
所以CyclicBarrier核心就是将通过将线程阻塞在条件队列,实现栅栏功能。
Semaphore
信号量就太简单了,就是翻版的ReentrantLock,可以自行使用。
总结一下他们三个底层的核心,
CountDownLatch改造AQS,通过状态State来将线程锁在同步队列,到达值得时候将线程放出。
CyclicBarrier使用ReentrantLock和Condition将线程锁在条件队列,和CountDownLatch效果不同的是,可以在释放线程时执行构造器传入的代码。并且可重复使用,这是CountDownLatch所不能做到的
Semaphore翻版得ReentrantLock,但是ReentrantLock只有一个资源能使用,Semaphore有多个资源使用