在AQS中最重要的是一个state变量。private volatile int state;
这个state的意思由继承这个类的子类来决定,用来表示锁的状态,如在ReentrantLock中可以使用0表示没有线程持有该锁,1表示该锁已经被其他线程持有。当然在可重入锁的实现时还会进行累加,代表当前线程持有锁的数量,这些在接下来都会一一讲到。
需要子类覆盖的流程方法:
同步状态state
在解读AQS之前,先来了解一下它的数据结构,它有一个内部类Node,该内部类定义如下:
static final class Node {
static final AbstractQueuedSynchronizer.Node SHARED = new AbstractQueuedSynchronizer.Node();
static final AbstractQueuedSynchronizer.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 AbstractQueuedSynchronizer.Node prev;
volatile AbstractQueuedSynchronizer.Node next;
volatile Thread thread;
AbstractQueuedSynchronizer.Node nextWaiter;
}
在AQS中是有一个同步队列的,这个同步队列的每个节点Node的定义如上,在这个node中保留了一个Thread线程,所以这个队列是一个线程队列,也就是说AQS会把申请锁的线程封装成一个node类型的节点,加入到同步队列中。而且还有两个prev和next分别指向前驱节点和后继节点,也就说明了这是一个双向队列。
在AQS中还有一个内部类,ConditionObject实现了Condition接口
public class ConditionObject implements Condition, Serializable {
private static final long serialVersionUID = 1173984872572414699L;
private transient AbstractQueuedSynchronizer.Node firstWaiter;
private transient AbstractQueuedSynchronizer.Node lastWaiter;
private static final int REINTERRUPT = 1;
private static final int THROW_IE = -1;
为什么要使用notifyAll和signal:
condition对应的是等待队列,一个condition对应一个等待队列。而wait和notify中同样维护了一个同步队列,但是它只有一个等待队列。在调用wait进入等待队列的时候,不能确保哪个线程处于该等待队列的头结点,所有唤醒的时候如果只是使用notify唤醒一个头结点,有可能会出现信号丢失,也就是唤醒了错误的线程(该线程唤醒后发现条件不满足,又继续进行等待。)。所以要使用notifyAll把所有线程都唤醒。然后这些线程根据条件判断是否运行还是继续等待。
而在condition中只需要使用signal就可以,这是因为挂在某个condition队列中的节点都是满足被唤醒的条件的,所以只需要唤醒一个头节点即可,唤醒多了也没有用。
await方法:
当一个线程获取锁后,调用了await方法,将调用await方法的节点从同步队列中移除,并把它放到等待队列的尾节点。
signal方法:
当一个节点被通知时,它会从等待队列中被移出,然后尝试去获取锁,如果获取不到,就会被加入同步队列中等待后续的获取。
锁的可重入释放锁的实现原理
我们知道,ReentrantReadWriteLock是有两个锁,读锁和写锁,那么如何通过一个标志位state来同时表示读锁和写锁呢,int类型的在计算机中是有32位的,就使用高位的16位表示写锁状态,低16位表示写锁的状态。对于写锁来说,只能被一个线程获取到,写锁的重入也和上面ReentrantLock的原理一致。但是读锁可以同时被很多个线程获取到,在高16位中可以表示被哪些线程获取了,那么每个线程重入了几次该如何表示呢,那么就需要每个读锁的线程维护一个自己的ThreadLock,在这里面保存每个线程的重入状态。
第一次A线程获取锁的时候,非公平锁调用AQS的acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
然后acquire内部调用ReentrantLock中的内部类NonfairSync中的tryAcquire方法,其实是调用了nonfairTryAcquire方法,
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;
}
如果当前锁的标准是0,代表可以获取锁,就通过CAS将标志位state改为1,并且将当前线程设置为独占锁的线程,返回true结束。这时A线程已经获取到了锁,并开始运行。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这时尝试获取锁会失败tryAcquire为false,就会执行addWaiter(Node.EXCLUSIVE),将该线程加入等待队列
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;
}
首先将当前线程封装成一个Node,一开始头结点head和尾节点tail都为空,所以直接进入enq()方法
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;
}
}
}
}
这个方法中是一个自旋,尾节点如果为空,就CAS创建一个头节点节点,并且head和tail都指向这个节点,如上图步骤2
第二次自旋,将当前线程的节点的前驱指针指向上一步创建的头结点,然后将tail指向当前线程节点,并把头节点的尾指针指向当前线程节点,这样就把当前线程节点加入到了同步队列中,如上图步骤3.
addWaiter方法执行完之后会返回一个Node,然后传入到acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法中,传入的Node再试图抢一下锁或者阻塞。
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
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);
}
}
对获取锁失败的节点,检查并且更新状态,判断是否需要阻塞
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)//如果前驱节点的等待状态为SIGNAL,表示当使用锁的线程释放锁的时候会通知它来运行,那么node就可以进行阻塞了,因为它的前驱节点还在等待中。
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {//如果前驱节点状态为cancelled,就把前驱节点从同步队列中移除掉
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
如过前驱节点不是SIGNAL状态和CANCELED状态,就执行compareAndSetWaitStatus方法去改变前驱节点的状态
/**
* CAS waitStatus field of a node.
*/
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
}
unsafe.compareAndSwapInt这个方法会将前驱节点的状态改为SIGNAL。图中红色的为阻塞线程
这是shouldParkAfterFailedAcquire的作用,将前驱节点的状态改为SIGNAL,并且返回true。然后判断parkAndCheckInterrupt,该方法将当前线程挂起。并且返回是否被中断过,如果有被中断过,这个线程被唤醒后,就会响应中断 interrupted = true;
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
release方法首先会调用tryRelease方法,主要有两个作用,将当前拥有锁的线程设为null,并将state赋值为0.
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);//获取锁的线程为null
}
setState(c);//state=0
return free;
}
接下来要唤醒处于等待队列中挂起的线程,release方法继续执行,判断 if (h != null && h.waitStatus != 0)head是否为空,并且waitStatus!=0.此时head不为空,并且waitStatus为SIGNAL就是-1.所有要执行unparkSuccessor(head)
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {//传进来的是head头节点
int ws = node.waitStatus;
if (ws < 0)//头节点的waitStatus为SIGNAL就是-1.改为0就是initialized
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {//这里是当后继节点为空或者被取消了,就有将后继节点移除出去。
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)//这里从尾部开始遍历,是为了防止断链。因为在将一个节点放入
//到队列的时候,需要三步,1.将当前节点的pre指向前驱节点,2.将tail指向当前节点,3.将前驱节点的Next指向当前节点。由于这三步不是一个原子操作,有可能将 前驱节点取消的时候第三步还没有完成,这个时候就断链了。
if (t.waitStatus <= 0)
s = t;
}
if (s != null)//s就是B节点
LockSupport.unpark(s.thread);//将B节点的线程唤醒。
}
将B线程唤醒之后,由于当初B线程是在方法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;
}
if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())//阻塞在这里了,现在唤醒了
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
自旋再次 if (p == head && tryAcquire(arg)),此时tryAcquire就可以拿到锁了。拿到锁之后,将当前节点设为头节点,并把之前的节点删除了。如图: