#Java—多线程7 Lock2—深入理解AQS
##独占锁的获取
非公平锁
1.模板方法aquire(int arg)
public final void acquire(int arg) {
//调用tryAcquire再次尝试获取锁,如果成功直接退出
//如果失败,首先调用addWaiter将钱财封装成结点入同步队列,
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
**tryAcquire(arg)-------:**再次使用CAS尝试获取同步状态,若成功方法直接返回,当前线程置为持有锁状态,
如果失败,调用
addWaiter(Node.EXCLUSIVE), arg)————将当前线程封装成Node结点后尾插入同步队列
//Creates and enqueues node for current thread and given mode.
//将当前的线程封装成结点入队
private Node addWaiter(Node mode) {
//1.将当前线程以指定模式(独占式、共享式)封装成Node结点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//2.拿到当前队列的尾结点
Node pred = tail;
//当前队列不为空
if (pred != null) {
node.prev = pred;
//将当前节点使用CAS尾插入同步队列
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//当前队列为空或CAS尾插失败是调用enq
enq(node);
return node;
}
mode:表示锁时什么模式,是独占式还是共享式。
enq()————当同步队列为空时,完成队列的初始化操作,以及不断CAS将当前节点未插入同步队列
private Node enq(final Node node) {
//不断自旋
for (;;) {
//拿到尾结点
Node t = tail;
//当前队列为空
if (t == null) { // Must initialize
//完成队列初始化操作,Lazy—load
if (compareAndSetHead(new Node()))//头结点不放数据,作为一个起始位置
tail = head;
} else {
//将当前节点尾插入链表
node.prev = t;
//不断将当前节点使用CAS尾插入同步队列中直到成功为止
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
aquireQueued() :
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//设置终端状态,默认为false
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);
}
}
注:
1.结点从同步队列获取同步状态的前提条件:
只有前驱结点是头结点时,线程才有机会获取同步状态
if (p == head && tryAcquire(arg))
**将结点置为头结点 setHead() **
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
2.当前线程获取同步状态失败时 调用——
boolean shouldParkAfterFailedAcquire(Node pred, Node node)
尝试将前驱结点状态改为Signal,表示当前节点应该被阻塞;否则一直在aquireQueued方法中一直自旋将此节点的前驱结点置为SIGNAL
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前驱结点状态
int ws = pred.waitStatus;
//如果前驱状态为 Node.SIGNAL (-1 当前节点的后继结点为等待状态)
if (ws == Node.SIGNAL)
//表示应该将当前节点阻塞
return true;
//前驱结点被取消
if (ws > 0) {
//一直向前找到节点状态不是取消状态的结点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {//前驱结点状态不大于0
//将前驱结点状态设置为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
注意:
1.在同步队列中,B 前驱结点是头结点的结点,但是B不一定可以先获取到锁。
如果现在有A线程持有锁 BCD处于等待状态
现在A线程释放锁,但是又有为入队的DEF线程和B一起竞争锁,没竞争到锁的线程继续入队
这时非公平锁
独占式锁的释放
public final boolean release(int arg) {
if (tryRelease(arg)) {
//获取当前同步队列的头结点
Node h = head;
//当前头结点的状态不为0.
if (h != null && h.waitStatus != 0)
//唤醒头结点的下一个结点
unparkSuccessor(h);
return true;
}
return false;
}
unparkSuccessor() 唤醒据头结点最近的一个非空结点(保证公平性)
private void unparkSuccessor(Node node) {
//获取头结点的状态
int ws = node.waitStatus;
//如果状态小于0,将状态设置为0
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);
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}