AQS和ReentrantLock源码解析
1. AQS
AQS实现的是排队和阻塞的机制。内部使用了Node节点的链表(CLH队列),AQS是JUC的基础。
1. 内部结构
它的同步方式有两种:
- 独占模式:资源是独占的,一次只能一个线程获取。
- 共享模式:同时可以被多个线程获取。
这两种模式都定义在Node节点里面,下面是Node节点的源码:
static final class Node {
static final Node SHARED = new Node(); 共享标记
static final Node EXCLUSIVE = null; 独占标记
static final int CANCELLED = 1;
static final int SIGNAL = -1; 表示后继节点需要被唤醒
static final int CONDITION = -2; 表示等待某个条件
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread; 对应的线程
Node nextWaiter; 等待队列里下一个等待的节点
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
可以看出里面封装了线程。这里注意一下,通过Node节点可以实现两个队列,一个是prev和next实现的同步队列(双向的);另一个是nextWaiter实现的等待队列(单向的,这个主要用在Condition条件上)
AQS的设计是基于模板方法模式,具体实现交给子类,可以按需要实现相应的方法
独占锁获取
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
独占锁释放
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
共享锁获取
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
共享锁释放
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
2. 资源的获取
资源的获取方法是acquire(int arg),arg是资源数量。线程在进入同步队列前还有一次获得锁的机会,这也是后面ReentrantLock公平锁实现的关键。
public final void acquire(int arg) {
在进入同步队列前还有一次锁获取的机会
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先调用tryAcquire方法尝试获得资源(需要子类实现),如果获得资源失败了就会调用addWaiter方法把当前线程放到同步队列的尾节点,传入的Node参数是独占的,下面是addWaiter方法的源码:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
通过自旋CAS更新尾结点
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
addWaiter方法会先尝试一次快速的CAS更新尾结点,如果没有成功的话就调用enq方法进行自旋CAS去更新尾结点。调用addWaiter方法把新节点放到队列尾后,就会调用acquireQueued方法,从头结点开始获得资源,下面是该方法的源码:
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;
}
如果可以休息就进入waiting状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
parkAndCheckinterrupt方法内部调用了LockSupport类的park方法来阻塞当前线程,unpark方法则是停止阻塞。shouldParkAfterFailedAcquire方法其实就是要保证当前节点的前驱结点是SIGNAL状态,等到资源释放的时候就会调用unpark方法把后继节点唤醒。有兴趣的可以自己查看源码,这里就不讨论了。
节点进入了队列之后,如果获取资源失败就会被阻塞,只有头结点是活跃的,资源释放的时候会唤醒它的后继节点。
这里只讨论了独占资源获取的方法,其他的共享资源获取和可中断获取就不一一讨论了,有兴趣可自行查看源码。
3. 资源释放
资源的释放相对来说就简单很多了,前面也有提到过,下面还是源码:
public final boolean release(int arg) {
尝试释放资源,还是子类去实现
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
唤醒线程
LockSupport.unpark(s.thread);
}
4. AQS的Condition
AQS的条件阻塞和唤醒是基于ConditionObject来实现的,可以通过await方法进行阻塞,signal方法唤醒线程,下面是它的部分源码:
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
条件队列的头节点
private transient Node firstWaiter;
条件队列的尾节点
private transient Node lastWaiter;
}
ConditionObject只定义了头结点和尾结点,等待队列的next节点其实就是Node节点的nextWaiter,这个前面有说过,下面就简单分析一下它的await方法和signal方法。
first传进来的其实就是等待队列的头结点
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
把节点放到同步队列的尾部,这个方法前面有说过
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
唤醒线程
LockSupport.unpark(node.thread);
return true;
}
//await方法
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
把线程放到等待队列的尾部
Node node = addConditionWaiter();
释放锁
long savedState = fullyRelease(node);
int interruptMode = 0;
判断是否在同步队列里,如果不是就阻塞线程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
2. ReentrantLock
1. Sync类
AQS讲完了,就可以开始讲ReentrantLock了,前面有说过AQS是基于模板方法模式的,有部分方法并没有实现,而是留给了子类去实现,Sync则是它的子类,而Sync的lock方法又留给了子类NonfairSync 和FairSync 去实现,这个两个类是ReentrantLock公平锁和非公平锁的基础。下面简单给出Sync类的代码:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
留给子类实现对应的加锁
abstract void lock();
非公平锁的获取
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
无锁获取,通过CAS获取锁
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;
}
锁的释放,公平锁和非公平锁是一样的
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;
}
}
非公平锁的获取其实还是调用AQS的acquire方法,不公平在于同步队列只有头结点可以争锁,但是其他还没进入同步队列的线程也可以争,这样就对同步队列里的其他线程不公平,源码也十分的简单
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
如果无锁则通过CAS获取,失败了就进入同步队列里等待锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
调用父类的非公平锁获取方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
公平锁:老的线程排队使用锁,新的线程仍然要进入同步队列里排队使用锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
没有非公平锁那样的CAS操作,全部都要放到同步队列里
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
这里判断队列是否还有节点也是公平锁的保证
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}