关于AQS–简单的认识
本人学习笔记,仅供学习参考 ,如有错误,望各位大佬指出
AbstractQueuedSynchronizer,从字面的意思来看,AQS==抽象的队列同步器
以ReentrantLock为示例,一起看看AQS
核心点
- 自旋
- cas
- locksupport(阻塞)
- 队列
重要变量及方法:
node: 内部类
- int waitStatus 节点状态
- 初始状态 0
- CANCELLED 1 中断或出错了,后面将会GC调
- SIGNAL -1 可唤醒
- CONDITION -2
- PROPAGATE -3
- node prev 前驱节点
- node next 后驱节点
- thread 当前节点入驻的线程(引用)
State:当前同步器状态
- 可重入的控制
- 独占锁与共享锁的控制
- ReentrantLock 独占锁 (它是只有是0才能够获取锁)
- Semaphore 共享锁 (它是设置State的值,如=5,每次获取锁,state减去相对信号量,直到为0或资源不足,就不能获取)
exclusiveOwnerThread 记录当前持有该同步器的线程
compareAndSetState(旧的预期值,新值)cas比较及交换
- 自旋中基本都有他的影子
hasQueuedPredecessors() 判断是否有现场在排队
公平锁加锁的简单流程
先看看获得锁及阻塞的源码
(1)acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) && //尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //这里分两步,第一步入队,第二部阻塞
selfInterrupt();
}
(2)tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //获取当前线程
int c = getState(); //获取当前同步器状态(state)
if (c == 0) { //等于0才进入获取锁
if (!hasQueuedPredecessors() && //公平锁,先校验有没有人在排队,有则入队去吧你,!!!非公平少了这一步
compareAndSetState(0, acquires)) { //采用cas改变state的值
setExclusiveOwnerThread(current); //设置当前进程拥有锁
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //这里就是一个可重入的逻辑了
int nextc = c + acquires; //state已经为1,也是持有当前同步器的线程,那就state再加1呗.
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
(3)addWaiter
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //new 一个新节点,指向入队线程
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) { //当前队列已经有人在排队拉
node.prev = pred;//下面的逻辑大概就是将tail节点变为我们new的node节点,然后将我们new的node节点变为tail节点
if (compareAndSetTail(pred, node)) { //只不过这里要用cas,防止多个线程变换,数据混乱了
pred.next = node;
return node;
}
}
enq(node); //当线程第一次入队,走enq(node)逻辑
return node;
}
//end(node) 这里就比上面多了一步初始化的操作
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head; //头尾节点初始化
} else { //逻辑大概就是将tail节点变为我们new的node节点,然后将我们new的node节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
(4)acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false; //是否被中断过,唤醒线程有unpark,中断的方式
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) && //首先先将头节点设置为-1状态,也就是可唤醒状态
parkAndCheckInterrupt()) //随后调用LockSupport.park(this);阻塞
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
(5)shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* 这个节点已经是可唤醒状态了,可以将他置为阻塞状态了
*/
return true;
if (ws > 0) {
/*
* 该节点已被我标记为取消,可以跳过该节点了(将它回收掉了)
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus必须为0或传播状态。表明我们
* 需要信号,但先别停车。继续开车,否则我跳下去人可能没了,将老司机感化掉,变为可唤醒状态,方可停车
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); //cas将waitStatus设为SIGNAL状态 -1.
}
return false;
}
(6)parkAndCheckInterrupt
//基本到这里就阻塞在那个自旋里面了,除非有线程被唤醒或者中断,又或者等太久了有特殊处理被剔除。
//等待一个有缘人来唤醒了。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
再看看唤醒的源码
(1)release
public final boolean release(int arg) {
if (tryRelease(arg)) {//这就就是释放同步器的状态啦,state = 0,exclusiveOwnerThread = null
Node h = head;
if (h != null && h.waitStatus != 0)//这里就到了那老司机必须为可唤醒状态了,否则下不了车
unparkSuccessor(h); //唤醒线程
return true;
}
return false;
}
(2)tryRelease
protected final boolean tryRelease(int releases) {
int c = getState() - releases; //state = 1 = 1-1 = 0
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null); //exclusiveOwnerThread = null
}
setState(c);
return free;
}
(3) unparkSuccessor
private void unparkSuccessor(Node node) { //这里的node为头节点
// node = head
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); //唤醒前先将节点状态置为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); //唤醒线程咯。
}
代码的循序跟着流程图来的,跟着看看就好,各位都是大佬。
就简简单单到这里咯,本人能力有限,如有理解错误,望指正,各位大佬们。