AbstractQueuedSynchronizer(后面我们使用AQS做简称)是什么?
Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues.This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic int value to represent state.
上面的JDK注释中对AQS的介绍,AQS是一个基于先进先出等待队列来实现阻塞锁和相关同步器的框架。这个类是那些使用整数来代表状态的同步器的有用的基。(个人翻译,可能不是很准确)。这里说是框架,其实就一个类。
那到底在什么情况下会使用到AQS呢?
在日常的编程中我们经常会碰到并发的情况。有并发,就有资源共享;有资源共享就需要处理资源同步访问。处理同步的时候,就要处理竞争发生时候的等待问题以及竞争解除后的唤起的问题。AQS就是一个便于我们实现这种同步机制的框架。我们日常中使用到的ReentrantLock、ReentrantReadWriteLock以及ArrayBlockingQueue等都是基于AQS实现的。
如果没有AQS的话,会怎么样?
我们可以设想一下,在没有AQS的情况下,我们要实现锁需要怎么做呢?
- 锁的状态是如何记录的,如何表示这个锁被持有的情况
- 如何获取锁
- 锁获取不到如何处理,直接返回,还是等待锁被释放。
- 如何释放锁
- 锁释放后,如何唤起哪些在等这个锁的线程。
- 共享锁、排他锁、公平锁、非公平锁,这些锁的特定如何处理。
上面这些问题,都是我们在实现锁的时候都要考虑的事情
在AQS的情况下,又会是怎么样的呢?
AQS作为基础类,主要解决了在锁不能获取的情况下的等待,以及锁释放后的唤起。锁状态的定义,如何获取锁以及如何释放锁,都是需要相应的同步机制自己实现的。所以在使用AQS的时候,需要实现下面几个方法:
- tryAcquire(),获取排他锁
- tryRelease(),释放排他锁
- tryAcquireShared(),获取共享锁
- tryReleaseShared(),释放共享锁
- isHeldExclusively(),是不是持有排他锁
类似ReentrantLock,使用state来标识锁的状态,state = 0表示锁没有被获取,当state > 0表示锁已经被获取了。另外实现了AQS的tryAcquire()和tryRelease()来处理state的状态,来处理锁的状态。这样就可以实现了最基础了ReentrantLock了。所以在AQS的加持下,实现类似的阻塞锁非常的方便。
上面我们介绍了AQS是什么,以及AQS具体的使用场景,下面我们详细介绍下AQS具体的实现机制。
我们先简单看下AQS的核心流程。
下面我们结合ReentrantLock来详细看下基于AQS如果实现排他锁的。
首先我们来看下ReentrantLock是如何用int类型的state来表示锁的状态的。state = 0表示锁没有被获取。state > 0表示锁已经被持有了。因为是可重入的锁,state表示了这个锁被同一个线程获取了多少次。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
......
}
public boolean lock() {
return sync.lock(1);
}
public void unlock() {
sync.release(1);
}
}
上面的代码片段是ReentrantLock核心代码,为了方便解释,把其他的代码都省略掉了。首先我们可以看到,在使用AQS的时候,一般不是直接实现AQS,而且创建一个内部的辅助类。
Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class
然后加锁和释放锁对应的lock和unlock,直接调用辅助类的lock和unlock。
下面我们看下Sync类的代码。
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //获取当前的线程
int c = getState(); //获取当前的状态 0表示没有人使用这个锁
if (c == 0) {
if (compareAndSetState(0, acquires)) { //通过cas的方式进行锁的获取
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;
}
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;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
}
- lock方法被当做一个抽象方法,具体的公平锁和非公平锁会有对应的实现。
- nonfairTryAcquire ,是一个非公平锁获取锁的方式,通过状态来判断锁是不是被持有,如果没有被持有则通过cas方法来获取锁,成功后设置持有锁的进程;如果已经被持有,则检查是不是被当前线程持有。
- tryRelease,释放锁,更新状态,如果已经全都释放了,则更新持有锁的进程。因为是排他锁,在设置state的时候,并没有使用cas的方案。
- isHeldExclusively 表示是不是被当前线程占有了。
上面我们来看下非公平锁NonfairSync的实现。
static final class NonfairSync extends Sync {
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
@ReservedStackAccess
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
- lock,获取锁,如果当天状态是0,表示没有线程持有这个锁,则尝试获取锁,获取成功后直接当前持有的线程,否则调用AQS中的acquire进行锁的竞争。对于公平锁来说,还要去检查当前是不是还有线程在等待队列中,如果有的话,则会acquire排到队列后面。
- tryAcquire,尝试获取锁,调用了上面说过的nonfairTryAcquire方法,这个方法是会被AQS调用的。
下面我们来看下AQS中的acquire方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- tryAcquire就是子类实现,尝试获取锁。如果获取锁成功,则直接返回了。如果获取失败,通过addWaiter加入到队列,并且通过acquireQueued进行挂起等待。
- acquireQueued,首先尝试获取锁,失败的话,则通过parkAndCheckInterrupt进行挂起。
上面看了获取锁的方法acquire,下面看下释放锁的release。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- 释放锁的就比较简单,首先调用子类实现的tryRelease,尝试释放锁。如果成功,唤起上面通过parkAndCheckInterrupt被挂起的线程。唤起后会重新尝试唤起锁,获取失败会被重新挂起。
总结以下,AQS通过tryAcquire以及tryRelease两个方法,来进行锁的获取以及释放,两个都是非阻塞的方法。如果获取成功则返回,如果获取失败,则添加队列。在队列中获取锁,获取失败则挂起,等待锁被释放后被重新唤起。唤起后还是会去尝试获取锁。释放锁的时候如果检查到已经全部释放,则会唤起被挂起的线程。这样通过tryAcquire和tryRelease,实现了锁的获取以及等待,以及锁的释放。具体锁状态的控制,子类则是通过tryAcquire和tryRelease进行控制的。
上面在介绍ReentrantLock的时候,把很多代码给简化了。大家可以了解基本的原理后,再去读源码会事半功倍。