20210811_AQS6个核心操作图解总结
1概述
1.1流程时序
抢锁-->抢锁失败入队-->Park阻塞-->放弃入队即出队-->任务完成,释放锁-->唤醒同步队列中的兄弟。
1.2核心6个操作
1.2.1抢锁
// 非公平锁
final void lock() {
if (compareAndSetState(0, 1)) // 不管三七二十一,先获取一下锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 回看公平锁
}
// 公平锁
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败,则以独占方式加入队列
selfInterrupt();
}
首先,看锁标志位state(默认0),T1线程抢到锁,则基于CAS机制修改标志位,设置独占线程,如下图:
image-20210811154659834.png其次,又有线程T2来抢锁
- 是自己锁的线程T1,则是重入,计重入次数+1
- 是其他线程T2,抢锁失败,则进行入队,具体如1.2.2。
- 公平锁判断依据:hasQueuedPredecessors,看等待区有没有人
1.2.2入队
这里,waitStatus:我们陈之为闹钟。为唤醒使用,CLH算法(效率高,能充分利用cpu),默认0(初始状态)-->1(取消)-->-1(可以唤醒下一个了)
普通队列特点:线程1顺利抢到锁,第一个节点则为线程2节点。(第一个线程执行,第二个线程放入队列,所以队列中节点1对应线程1节点
AQS队列特点:第一个节点为空节点,第二个节点才为入队的首个线程2节点。
- 我们知道,AQS同步队列中目前没有节点,则要首先New一个新的节点,然后会初始化一个NULL空节点。这点一定要理解。
-
队列中目前已有节点,则尾部插入。
-
中部插入(aqs不存在)
1.2.3阻塞(park)
image-20210913214840806.png1.2.4释放锁
- 修改锁标志位state
- 从同步器头结点head开始,unparkSuccessor(head) ,h.waitStatus等待闹钟的状态变化-1(需要唤醒后继节点)-->0(回归初始状态)
- 找到唤醒节点s,LockSupport.unpark(s.thread); // 唤醒线程T2,通过步骤2和步骤3我们知道要对等待闹钟waitStatus进行2次操作
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) {
/*
* 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) // 等待闹钟:-1-->0,然后释放
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)
if (t.waitStatus <= 0)
s = t;
}
if (s != null) // 唤醒后继节点
LockSupport.unpark(s.thread);
}
1.2.5唤醒(T2线程)
//1.parkAndCheckInterrupt
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
// 2.selfInterrupt(); os给唤醒的线程中断信号
// 3.T2线程继续执行自己的业务逻辑
image-20210913214923492.png
1.2.6出队(过号)
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) // 这里,failed=true,why todo
cancelAcquire(node);
}
}
网友评论