第一篇:源码分析AbstractQuenedSynchronized(一)
第二篇:源码分析AbstractQuenedSynchronized(二)
文章目录
本文主要结合AQS分析JDK并发包中的几个非常有用的并发工具类:CountDownLatch、CyclicBarrier和Semaphore.
CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作。
其内部依赖一个AQS的实现类Sync实现功能,如下图所示
使用示例
import java.util.concurrent.CountDownLatch;
public class CountDownLatch_Test {
static CountDownLatch t3= new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
Thread work1 = new Thread(new Runnable() {
public void run() {
System.out.println("任务1处理结束");
t3.countDown();
}
});
Thread work2 = new Thread(new Runnable() {
public void run() {
System.out.println("任务2处理结束");
t3.countDown();
}
});
work1.start();
work2.start();
t3.await();//阻塞,直到count减为0
System.out.println("两个任务处理结束!");
}
}
图片源于https://javadoop.com/post/AbstractQueuedSynchronizer-3
构造函数
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);//count实际被设置为同步状态的state值
}
private final Sync sync;
/**
* 使用内部类Sync来实现功能
*/
private static final class Sync extends AbstractQueuedSynchronizer {
//构造函数:将state设置为count值
Sync(int count) {
setState(count);
}
//该方法用于判断state是否为0
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//用在countDown方法中,将state减1,然后判断state是否为0
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
CountDownLatch的重点其实就是await和countDown 两个方法,我们先分析await方法,看看线程是如何被挂起的
await方法(进入该方法到线程挂起)
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/*====================AQS:acquireSharedInterruptibly==============================
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//检查到线程中断,则直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//state!=0,则直接从await方法返回,否则进入doAcquireSharedInterruptibly方法
if (tryAcquireShared(arg) < 0)//return (state == 0) ? 1 : -1;
doAcquireSharedInterruptibly(arg);
}
=====================================================================================*/
/*====================AQS:doAcquireSharedInterruptibly==========================
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
//当前线程node加入阻塞队列末尾,状态为SHARED,由此我们看出调用await方法的线程一开始都会被加入阻塞队列中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//只要state不等于0,这个值返回-1
int r = tryAcquireShared(arg);//return (state == 0) ? 1 : -1;
//第一次进入await方法时,r应该是为-1的,这个if代码块主要是线程被唤醒后的后续处理过程,等线程被唤醒后我们再回头看里面的逻辑
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//调用await方法,正常情况下第一次走这里的逻辑,在这里面线程会被挂起,等待唤醒
if (shouldParkAfterFailedAcquire(p, node) &&//该方法为AQS方法,主要将node的前驱结点p设为SINGNAL状态
parkAndCheckInterrupt())//该方法为AQS方法,利用LockSupport工具挂起当前线程,同时返回线程的中断状态
throw new InterruptedException();//如果进入这个逻辑,则说明线程中断了,抛出中断异常
}
} finally {
if (failed)
cancelAcquire(node);
}
}
=======================================================================================*/
在shouldParkAfterFailedAcquire方法中线程被挂起后,我们接下来就可以看线程是如何被唤醒的,即分析countDown方法
countDown方法
public void countDown() {
sync.releaseShared(1);
}
/*=============AQS的releaseShared方法=========================
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//将state减1,然后判断state是否为0
//state为0,准备唤醒线程
doReleaseShared();
return true;
}
//state不为0,直接返回
return false;
}
================================================================== */
/*=============AQS的doReleaseShared方法,state==0时才会进入该方法=========================
private void doReleaseShared() {
for (;;) {
Node h = head;
//h为null说明阻塞队列为空,h=tail说明head后面没有结点在排队,即阻塞队列的结点都被唤醒了,因此这两种情况不需要继续唤醒后继结点了
if (h != null && h != tail) {
int ws = h.waitStatus;//还记得分析await时的shouldParkAfterFailedAcquire方法吗?
//等待线程t3入队的时候已经将它的前继结点的waitStatus设为SINGAL(-1)了,所以head的状态为SINGAL
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // CAS失败则重新循环
unparkSuccessor(h);//唤醒head的后继结点t3
}
else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
================================================================== */
await方法(state==0后被唤醒)
//回顾await方法(进入该方法到线程挂起),调用await方法的线程被唤醒后会进入这个if逻辑
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
/*=============AQS的setHeadAndPropagate方法,node为head的后继结点,propagate=1=========================
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//将即将要被唤醒的结点(head的后继结点)设为头结点
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//可以看出,阻塞队列的结点被唤醒后还会继续调用doReleaseShared方法唤醒队列中后面的结点
//调用doReleaseShared方法大概的过程是这样的:state减为0后,
//第一次从countdown方法中调用该方法会唤醒阻塞队列中的第一个结点,唤醒后的结点还会继续调用该方法去唤醒后面的结点
doReleaseShared();
}
}
================================================================== */
/*=============AQS的doReleaseShared方法,state==0时才会进入该方法=========================
private void doReleaseShared() {
for (;;) {
Node h = head;
//h为null说明阻塞队列为空,h=tail说明head后面没有结点在排队,即阻塞队列的结点都被唤醒了,因此这两种情况不需要继续唤醒后继结点了
if (h != null && h != tail) {
int ws = h.waitStatus;//还记得分析await时的shouldParkAfterFailedAcquire方法吗?
//等待线程t3入队的时候已经将它的前继结点的waitStatus设为SINGAL(-1)了,所以head的状态为SINGAL
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // 循环直到将head状态设为0
unparkSuccessor(h);//唤醒head的后继结点t3
}
else if (ws == 0 &&//这个CAS失败的场景是:执行到这里,刚好有一个结点入队,入队会将这个ws设为-1
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head)
//这是多次调用doReleaseShared方法后最终结束的地方,当最后一次调用doReleaseShared进入到这里时说明阻塞队列的所有结点都被唤醒,head后面没有结点了
break;
}
}
================================================================== */
CyclicBarrier
CyclicBarrier的字母意思是可循环使用的屏障。它的功能是:让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,打开屏障,所有在栅栏上的线程才会继续运行。
图片源于https://javadoop.com/post/AbstractQueuedSynchronizer-3
package java.util.concurrent;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class CyclicBarrier {
//CyclicBarrier与CountDownLatch相比可以重复使用,因此具有代的概念
private static class Generation {
boolean broken = false;
}
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
//屏障拦截的线程数量
private final int parties ;
//越过屏幕前优先执行的动作
private final Runnable barrierCommand;
//当前所属的代
private Generation generation = new Generation();
//还没到达屏障的线程,从parties递减到0
private int count;
/**
* 开启新的一代
*/
private void nextGeneration() {
// 唤醒在栅栏上等待的所有线程
trip.signalAll();
// 建立新的一代
count = parties;
generation = new Generation();
}
/**
* 打破栅栏
*/
private void breakBarrier() {
//设置标志位
generation.broken = true;
//重置count,唤醒栅栏上所有等待的线程
count = parties;
trip.signalAll();
}
/**
* Main barrier code, covering the various policies.
*/
private int dowait(boolean timed, long nanos)//timed:是否带有超时机制
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//该方法中会执行Condition的await方法,因此首先肯定要获得锁
lock.lock();
try {
final Generation g = generation;
//栅栏被打破,抛出异常
if (g.broken)
throw new BrokenBarrierException();
//线程中断,抛出异常
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;//调用一次await方法,count减1
//count==0,准备让栅栏上线程通过
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//如果设置了barrierCommand则优先执行barrierCommand
if (command != null)
command.run();
ranAction = true;//设置标志位,说明command.run()没有发生异常
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) {
//逻辑都这里,等待的线程在Condition的await方法中被中断
if (g == generation && ! g.broken) {
//仍然在当前代,之前栅栏也没有被打破过
//则,打破栅栏并抛出异常
breakBarrier();
throw ie;
} else {
//逻辑走到这里,说明新的一代已经产生了,即最后一个线程await执行完成,因此没有必要抛出中断异常,记录中断信息即可
Thread.currentThread().interrupt();
}
}
//醒来发现栅栏已经被打破,抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 这个 for 循环除了异常,就是要从这里退出了
// 我们要清楚,最后一个线程在执行完指定任务(如果有的话),会调用 nextGeneration 来开启一个新的代
// 然后释放掉锁,其他线程从 Condition 的 await 方法中得到锁并返回,然后到这里的时候,其实就会满足 g != generation 的
// 那什么时候不满足呢?barrierCommand 执行过程中抛出了异常,那么会执行打破栅栏操作,
// 设置 broken 为true,然后唤醒这些线程。这些线程会从上面的 if (g.broken) 这个分支抛 BrokenBarrierException 异常返回
// 当然,还有最后一种可能,那就是 await 超时,此种情况不会从上面的 if 分支异常返回,也不会从这里返回,会执行后面的代码
if (g != generation)
return index;
//醒来发现超时,打破栅栏,抛出异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;//count初始为parties
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
public int getParties() {
return parties;
}
//不带超时机制
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
//带超时机制,会抛出异常
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
//直接通过broken的值来判断栅栏是否被打破
/**
* 有以下几种情况,栅栏会被打破
* 1.线程中断
* 2.超时
* 3.执行指定barrierCommand操作时发生了异常
*/
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
public void reset() {
final ReentrantLock lock = this.lock;
//打破栅栏的过程中有一个步骤是唤醒等待线程,而signalAll操作是需要获得锁的,因此会进行加锁操作
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
//获得在栅栏上等待的线程数
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
}
Semaphore
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程以保证合理的使用公共资源。
使用示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
private static final int THREAD_COUNT=30;
private static ExecutorService threadPool= Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore semaphore=new Semaphore(10);//只允许10个线程同时并发
public static void main(String[] args) {
for (int i = 0; i <THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
public void run() {
try {
semaphore.acquire();//获取许可证
System.out.println("save data");
semaphore.release();//归还许可证
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
}
}
Semaphore的内部结构和ReentrantLock很相似,内部都有一个AQS的实现类Sync,Sync又有两个实现类,用于实现两种公平策略。
构造函数
//默认非公平锁
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//可通过构造参数指定公平策略
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
acquire方法
//下面四种方法主要区分在获取指定数量的资源以及是否响应中断
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
首先来看我们常用的acquire()方法,它的功能是响应中断式的获取一个资源(许可证)
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/*=============AQS类的acquireSharedInterruptibly方法============
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//检查到线程中断,则直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
//当state已经为0时即资源为空时,remaining<0,此时线程准备进入阻塞队列
doAcquireSharedInterruptibly(arg);
}
==================================================================*/
由于Sync有两个实现类,对应两种公平策略,它们都重写了tryAcquireShared(arg)方法,下面进行对比:
//==================非公平策略NonfairSync=======================
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//=================公平策略FairSync============================
protected int tryAcquireShared(int acquires) {
for (;;) {
//和非公平策略相比区别:首先会判断是否有线程在阻塞队列中排队,即head结点后面有结点在排队
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
当state为0后,再调用acquire方法就会进入下面的逻辑,线程加入到阻塞队列中,这个方法在CountdownLatch中介绍过,这里就不赘述了
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//当前线程node加入阻塞队列末尾
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//只要state不等于0,这个值返回-1
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//parkAndCheckInterrupt方法中线程挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
线程挂起后要等待其他线程调用realease方法释放资源,才能重新获得资源继续执行,下面看release方法
release方法
public void release() {
sync.releaseShared(1);
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
/*===================AQS:releaseShared=======================
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
=================================================================*/
//方法很简单,释放资源,state增加
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
/*这个方法在Countdownlatch中也介绍过,主要作用是唤醒阻塞队列中的结点
===================AQS:doReleaseShared=============================
private void doReleaseShared() {
for (;;) {
Node h = head;
//h为null说明阻塞队列为空,h=tail说明阻塞队列的结点都被唤醒了,因此这两种情况不需要继续唤醒后继结点了
if (h != null && h != tail) {
int ws = h.waitStatus;//head结点后面的结点在入队的时候已经将头结点的waitStatus设为SINGAL(-1)了
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // 循环直到将head状态设为0
unparkSuccessor(h);//唤醒head的后继结点
}
else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//说明
if (h == head) // loop if head changed
break;
}
}
====================================================================*/
在搞懂CountdownLatch的基础上,分析Semaphore十分轻松。
总结
- CountdownLatch和Semaphore的实现思路很相似,内部都有一个AQS的实现类Sync,都是将线程加入到阻塞队列中挂起,前者是多个线程调用countdown方法用于state递减,state减为0之前调用await方法的线程都加入到阻塞队列中挂起,当state减为0后阻塞队列中的线程被唤醒;后者是线程调用acquire方法将state递减,state减为0之前这些线程都能获得资源,state减为0后将线程都添加到阻塞队列中挂起,调用release方法增加state使其大于0后尝试去唤醒阻塞队列中的线程。
- CyclicBarrier主要通过Condition的实现类ConditionObject来实现,内部有个类Generation用来维护当前代的栅栏是否已被打破,在count(还没有达到屏障的线程数)减为0之前达到线程的屏障都会调用condition的await方法加入到条件队列中挂起等待,当count==0后,说明所有线程都已达到屏障,这时候就会释放屏障,唤醒所有等待队列中的结点,它们转移到阻塞队列中重新尝试去获得锁继续执行