上一篇讲到了synchronized实现原理,这一篇一起来学习一下Lock的底层实现原理。
java.util.concurrent包中有很多lock接口的实现类,ReentrantLock,ReadWriteLock,这在我的另一篇文章中也提到了。
这些实现类的内部都使用了队列同步器AbstractQueuedSynchronizer类作为依赖。下面我们用AQS来指代AbstractQueuedSynchronizer。
AQS定义:
public abstract class AbstractQueuedSynchronizer extends
AbstractOwnableSynchronizer implements java.io.Serializable {
//等待队列的头节点
private transient volatile Node head;
//等待队列的尾节点
private transient volatile Node tail;
//同步状态
private volatile int state;
protected final int getState() { return state;}
protected final void setState(int newState) { state = newState;}
...
}
AQS中使用int型变量state来表示同步状态(0表示空闲,1表示被占中),通过内置的队列实现线程的排队工作。同步状态state,队列头指针head,队列尾指针tail都使用了volatile进行修饰,保证了多线程之间的可见性。
内部队列的定义:
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;
...
}
队列中的每个节点存储了前缀节点prev,后缀节点next,节点中的线程thread,节点的状态waitStatus,状态的定义有四种,CANCELLED取消状态,SIGNAL等待触发状态,CONDITION等待条件状态,PROPAGATE状态需要向后传播。整个队列中,只有头节点中的线程是当前执行线程。
在并发的情况中,如果state的值为0,即状态为空闲,那么多个线程同时执行CAS修改state的值,成功修改state值的线程获得该锁。未获得锁的线程将生成新的节点,使用CAS的方式向后排队。当线程排到队列后面时,并不会马上插入,而是进行一个自旋操作。
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
因为在node的排队的过程,头节点中的线程(即之前在执行的线程)可能已经执行完成,所以要判断该node的前一个节点pred是否为head节点(代表正在执行线程),如果pred == head,表明当前节点是队列中第一个“有效的”节点,因此再次尝试tryAcquire获取锁,如果获取到了锁,就直接将该线程设置到头节点,否则,再进行判断:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* 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.
*/
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
如果前缀节点的状态为SIGNAL,表示这个节点已经设置了锁一释放就通知它的状态,那么我们就可以安全的将线程插在它的后面,并返回true。如果前缀节点的状态是取消状态,那么就跳过前缀节点,找到前面没有被取消的第一个节点,插在其后面。否则,前缀节点的状态要么是0,要么是状态向后发散,我们直接将当前节点的状态设置为SIGNAL。以上两种情况,都无须向队列中插入,所以均返回false。
线程每次被唤醒时,都要进行中断检测,如果发现当前线程被中断,那么抛出InterruptedException并退出循环。从无限循环的代码可以看出,并不是被唤醒的线程一定能获得锁,必须调用tryAccquire重新竞争,因为锁是非公平的,有可能被新加入的线程获得,从而导致刚被唤醒的线程再次被阻塞,这个细节充分体现了“非公平”的精髓。
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(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 p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
LockSupport.unpark(s.thread);
}
线程释放锁的过程:
如果节点的状态小于0.将节点的状态设为0。然后找到下面状态值小于0的线程,将其唤醒。
总结:
Doug Lea大神写的代码相当牛,只看源码,不看应用,很难完全理解透彻,但源码看下来总体思路还是很清晰的。