这篇文章主要通过并发包中的几个高级工具类来展示AQS的价值,具体AQS的详细解析,看另外一篇文章。
除了ReentrantLock之外,还有另外一下同步组件用到了AQS,例如
java.util.concurrent.locks.ReentrantReadWriteLock
读写锁、
java.util.concurrent.Semaphore
信号量、
java.util.concurrent.CountDownLatch
。我们再来看一看它们是如何使用AQS实现自己的能力。
AQS中的同步状态state在不同的类中被使用的方式不同。
ReentrantLock
在ReentrantLock被使用的方式是等于0的时候表示锁处于可获取状态,等于1的时候表示锁已经被获取,需要排队等待。
CountDownLatch
在CountDownLatch类中,state!=0表示倒计时还没有结束,此时调用await方法的线程将一直处于阻塞状态。因为在初始化CountDownLatch对象的时候,会设置一个初始值,每次调用countDown方法都会将state值减去1,直至减到0。我们来看一下几个关键方法
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
调用countDown方法后,会调用AQS中的模板方法releaseShared,模板方法会调用CountDownLatch中重写的protected修饰的tryReleaseShared方法。如果释放成功,也就是将state减到了0,那么回到AQS中调用doReleaseShared方法,来将同步队列中等待状态的线程唤醒,也就是最开始调用await方法获取锁失败的线程。下面是tryReleaseShared方法和doReleaseShared方法
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
看完了releaseShared方法,我们再来看看await方法,await调用AQS中的模板方法获取资源,模板方法调用CountDownLatch中实现的protected修饰的方法和排队等待方法。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
其实也能够想到,countDown方法用来释放资源,那么await方法肯定是用来获取资源,所以调用的是acquireShared方法。如果获取失败,那么调用AQS中的doAcquireSharedInterruptibly方法来将当前线程加入同步队列进行等待。事实上也基本上会获取失败
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
从这里能够看出,只要state不是0,那么就会获取失败,因为开始初始化CountDownLatch时会设置state的值为一个大于0的整数,调用countDown方法时,才会将state减去1。所以开始调用await方法的线程会等待倒计时结束才能恢复,这也就是CountDownLatch对象被使用的场景:一个线程等待其他多个线程执行完任务才恢复继续执行。
Semaphore
Semaphore在使用时也会将state初始化为大于0的一个整数,这一点跟CountDownLatch类似。但是,Semaphore中这个值表示资源的个数,也就是并发数。当调用acquire方法时,会将资源数减1,当减到0之后,再调用acquire方法就会将当前线程阻塞。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquire方法会调用AQS中的模板方法(前面已经展示过),这个模板方法会调用Semaphore自己重写的protected修饰的方法,我们可以看出,当资源数不够时,会返回负数,然后将当前线程加入同步队列中进行排队,并阻塞当前线程。
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
当调用release方法释放资源时,会调用AQS中的模板方法releaseShared,模板方法调用Semaphore中实现的方法tryReleaseShared来释放资源,其实就是把state的值加1。之后再调用AQS中的doReleaseShared方法来实现唤醒队列中等待的线程。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
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;
}
}
总结来说,信号量与重入锁都是实现控制并发的工具。信号量可以实现多个线程并发,也可以控制一个线程并发,只需要在初始化是将state设置为不同的整数。也就是说他能够实现重入锁实现的功能,而且功能更为强大。它们低层的实现都是通过AQS这一个类。
ReentrantReadWriteLock
读写锁低层也是用了AQS来进行实现。它与其他有所不同的是,它有两个同步状态。两个同步状态还是用一个state来进行表示。前面高16位表示读状态,低16为表示写状态。写锁的获取是独占式的类似于ReentrantLock,读锁的获取是共享式的,类似于Semaphore。详细的我就不再展开了,其实这些工具类都是自己实现了protected修饰的方法,而大量的方法,无论是共享的还是独占的,无论是模板的还是非模板的都是来自于AQS,可以通过阅读源码得知。