AQS介绍
我们在使用多线程的同步接口的时候,比如ReentrantLock
和Semaphore
,我们可以发现这两之间有许多共同点,这两个类都可以用作一个 阀门,即每次只允许一定数量的线程通过,当线程到达阀门时,可以通过(在lock()
和acquire()
时成功返回),可以等待(在调用lock()
和acquire()
时阻塞),还可以取消(在tryLock()方法和tryAcquire()方法返回 ‘false’)。
事实上,它们在实现时都使用了一个共同的基类 AbstractQueuedSynchronizer(AQS)
,这个类也是其他许多同步类的基类,所以各个同步类之间有一些共性。 AQS是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS很容易并且高效地构造出来。
除了
ReentrantLock
和Semaphore
是基于AQS构造的,CountDownLatch
、FutureTask
,SynchronousQueue
也是的
AQS解决了在实现同步器时设计的大量细节问题,所以可以用来高效的构建同步器。
在基于 AQS 构建的同步器类里面,最基本的操作包括各种形式的获取操作和释放操作。当使用 锁(Lock)和信号量(Semaphore) 的时候,获取操作的含义指的就是获取锁或许可,并且调用者会一直等待直到同步器类处于可被获取的状态。在使用 CountDownLatch的时候,获取就是指 等待并直到闭锁到达结束状态
。使用 FutureTask
的时候,获取就意味着 等待并直到任务已经完成。
AQS管理了 一个同步器类的状态,使用一个整数类型的值,可以通过getState()
和 setState(int newState)
和 compareAndSetState(int expect, int update)
来操作。
- 在
ReentrantLock
中,使用AQS的状态来表示所有者线程(即获取锁的线程)已经重复获取该锁的次数,它还创建了额外的状态变量来表示当前获取是重入还是其他线程在竞争。 - 在
Semaphore
用这个状态来表示剩余的许可数量。 - 在
FutureTask
中用这个状态表示任务的状态(未开始,正在运行,已完成以及已取消)
AQS的获取和释放操作,类似如下伪代码:
public boolean acquire() {
while(当前状态不允许获取操作){ //那么就自旋获取
if(需要阻塞获取获取){
就把当前线程放到某个阻塞队列里面
阻塞当前线程
}
else{
返回失败
}
}
可能需要更新同步器的状态(比如Semaphore就许可减少1个)
把本线程从队列里取出
返回成功
}
public void release(){
更新同步器的状态(比如Semaphore就许可减少1个)
if(新状态允许一个被阻塞的线程获取(比如当前信号量大于1就需要唤醒其他线程了)) {
解除队列中一个或多个线程的阻塞状态
}
}
然后 获取分为 独占获取(写锁与写锁之间)、共享获取(读锁和读锁之间),那么AQS提供了专门的方法,对于独占获取是tryAcquire(int arg)
和tryRelease(int arg)
和isHeldExclusively()
对于共享获取是 tryAcquireShared(int arg)
和tryReleaseShared(int arg)
和releaseShared(int arg)
在自己的同步器类中需要实现这些方法,AQS的 acquire 和 release 方法会根据子类实现的 tryxxx 方法来判断某个操作是否能执行。
java.util.concurrent包中同步器类的AQS
ReentrantLock
ReentrantLock 只支持独占方式的获取操作,所以它应该只实现了 tryAcquire(int arg)
和tryRelease(int arg)
和isHeldExclusively()
这些在独占方式使用的方法。
ReentrantLock 继承AQS实现了一个子类 Sync ,NonfairSync继承了Sync,是用于非公平的锁,FairSync继承了Sync,是用于公平的锁。
非公平的较为简单,我们先来看。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
我们前面已经知道,ReentrantLock
使用状态 state 来保存本线程锁获取 的次数。
看上面代码,首先检查锁的状态,如果c == 0(即当前没有被其他线程持有),那么就调用compareAndSetState
方法来原子更新状态,然后保存当前获取锁的线程。
如果c != 0(即当前锁已经有线程持有了),如果获取锁的线程就是持有锁的线程,那么就相当于重入地获取锁,状态+1(保存本线程锁获取 的次数),如果不是本线程,那就获取锁失败。
释放锁操作:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
只有 拥有锁的线程才能够释放锁,所以判断之后,设置状态,如果重入多次,那就需要一次次地解锁。
Semaphore
Semaphore
将 AQS 地状态state用于保存当前可用许可的数量:
因为许可一般都大于1,所以可以有多个线程执行获取锁操作,所以这是共享方式的获取操作,实现的是 tryAcquireShared(int arg)
和tryReleaseShared(int arg)
和releaseShared(int arg)
这几个方法。
也是先来看它的非公平实现
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
很明显这是自旋等待的获取许可,首先判断剩余许可数量,如果许可不足,那么机会返回一个负值表示获取许可失败,如果还有就会使用compareAndSetState
方法去修改状态,降低许可数量,执行成功就会返回一个正值表示获取操作成功
当没有足够的许可或者compareAndSetState
更新许可的操作成功,这个自旋循环就会终止。前者会因为其他线程释放操作而成功,后者可能是和其他线程竞争更新失败,不过最终一定会有一个线程竞争修改成功,那就会导致其他线程终止循环。
CountDownLatch
在 CountDownLatch
中,使用AQS的状态表示待完成的线程数量
public CountDownLatch(int count) {
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
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;
}
}
如图,先创建需要完成任务的线程数量作为AQS状态state,然后每个线程使用tryRelease去减少状态,当状态state为0的时候,获取操作才能成功。
ReentrantReadWriteLock
在ReentrantReadWriteLock
类中,有两个锁,一个读取锁,一个写入锁:
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
但是它使用一个AQS子类来同时管理读锁和写锁。它使用一个16位的状态来表示写入锁的计数,再用另一个16位的状态表示读取锁的计数。读取锁的状态的操作使用共享的方法,写入锁的操作使用独享的方法。
AQS在内部维护了一个等待线程的队列,记录了每个线程是独占访问还是共享访问的,ReentrantReadWriteLock
类利用了该特性,当锁可用的时候,它判断队列首的线程是要获取写入锁,那么就只有这个线程获取锁。如果队列首的线程获取读取锁,那么在队列中第一个要获取写入锁的线程之前都可以获得这个锁。