lock
- 公平锁(FairSync)
核心方法:
public final void acquire(int arg) {
//先去尝试加锁,加锁失败再加入队列
if (!tryAcquire(arg) &&
//当存在锁资源竞争的时候才会初始化aqs队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(arg)方法:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//State表示当前锁状态,锁空闲状态为0,每多一把锁+1(重入锁)
int c = getState();
if (c == 0) {
//判断是否有资格获取锁
if (!hasQueuedPredecessors() &&
//CAS操作获取锁
compareAndSetState(0, acquires)) {
//记录当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//重入锁 State+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
总结:通过CAS尝试获取锁,成功时返回true
hasQueuedPredecessors()方法:
public final boolean hasQueuedPredecessors() {
//这种方法的正确性取决于头在tail之前和head之前被初始化。
//如果当前线程是队列中的第一个线程,则next是准确的。
Node t = tail; // 按反初始化顺序读取字段
Node h = head;
Node s;
//当h==t时说明队列为空,初始化队列
//判断当前线程是不是头节点的第二个线程
//每个线程刚进入tryAcquire的时候如果前面有线程排队,会在这拦截
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
几种情况下会返回false:
- aqs队列没有初始化
- 队列初始化后,头节点的下一个节点存在并且头节点的下一个线程为当前的线程
总结:判断当前节点的thread是否为有资格竞争锁。
当锁存在竞争的时候:
addWaiter()方法:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 尝试快速路径的enq; 故障时备份到完整的enq
Node pred = tail;
if (pred != null) {
node.prev = pred;
//cas操作,赋值tail节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
//返回当前AQS队列的tail节点
return node;
}
}
//
enq(node);
//返回tail节点
return node;
}
enq()方法:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 必须初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
//node的prev赋值tail节点
node.prev = t;
//CAS,成为新的tail
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq的作用:
1、初始化队列
2、赋值最新的tail节点
3、采用自旋加CAS的方式保证了compareAndSetHead(new Node())和compareAndSetTail(t, node)成功,当addWaiter()的compareAndSetTail(pred, node)方法CAS失败时,进入enq()自旋加CAS
赋值tail节点。
总结:addWaiter()方法就是为了构建链表结构的队列。
acquireQueued()方法:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//判断当前node的上一个节点是否为头节点,如果等于头节点则有资格竞争锁
//在公平锁中如果是上一个unlock的节点唤醒的节点,那么一定会获得锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
//返回打断标志
return interrupted;
}
//自旋把上一个节点的ws赋值-1
if (shouldParkAfterFailedAcquire(p, node) &&
//park当前线程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire()方法:
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.
*/
//一个新节点第一次进入被赋值ws=SIGNAL,第二次返回true
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.
*/
//CAS修改上一个节点的ws
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
总结:当前节点总会把上一个节点的ws改为-1,自己当前为0,这样做的目的是告诉上一个节点自己不是最后一个节点,在unlock时如果有下一个节点则会唤醒它。