AQS源码探究_04 成员方法解析(释放锁、响应中断出队逻辑)

举报
兴趣使然的草帽路飞 发表于 2021/06/08 23:16:33 2021/06/08
【摘要】 AQS成员方法解析(释放锁逻辑) 1. unlock释放锁方法 // 位于RentrantLock中:释放锁的方法 public void unlock() { // 释放锁 sync.release(1); } // 位于AQS的静态内部类Sync中:真正释放锁的方法 // RentrantLock.unlock() -> sync.release() p...

AQS成员方法解析(释放锁逻辑)

1. unlock释放锁方法

// 位于RentrantLock中:释放锁的方法
public void unlock() { // 释放锁 sync.release(1);
}

// 位于AQS的静态内部类Sync中:真正释放锁的方法
// RentrantLock.unlock() -> sync.release()
public final boolean release(int arg) { // tryRelease尝试释放锁: // true: 当前线程已经完全释放锁 // false:当前线程尚未完全释放锁 if (tryRelease(arg)) { // head 什么情况下会被创建出来? // 当持锁线程未释放线程,且持锁期间有其他线程想要获取锁时,其他线程发现无法获取锁, // 且此时阻塞队列是空队列,此时后续线程会为当前持锁线程构建出一个head节点(将持锁线程封装入head) // 然后后续线程会追加到head节点的后面(成为head的后驱) Node h = head; // 条件1:h != null成立,说明队列中的head节点已经初始化过了,ReentrantLock在试用期间,发生过多线程竞争了~ // 条件2:h.waitStatus != 0 成立,说明当前head后面一定插入过node节点~ if (h != null && h.waitStatus != 0) // 唤醒后驱节点~ unparkSuccessor(h); return true; } return false;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

2. tryRelease尝试释放锁的方法

// 位于AQS的静态内部类Sync中:尝试释放锁方法
// true: 当前线程已经完全释放锁 | false:当前线程尚未完全释放锁
protected final boolean tryRelease(int releases) { // state状态变量的值相减 int c = getState() - releases; // 如果条件成立:说明当前线程并未持锁 -> 直接抛异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); // 当前线程持有锁 // 当前线程是否已经完全释放锁,默认初始值false boolean free = false; // c == 0条件成立时:说明当前线程已经达到完全释放锁的条件 if (c == 0) { // free = true 当前线程已经完全释放锁 free = true; // 更新当前持锁线程为null setExclusiveOwnerThread(null); } // 更新state(基于CAS) setState(c); // 返回free return free;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

3. unparkSuccessor唤醒后驱节点线程的方法

// 位于AQS中:唤醒后驱节点线程的方法
private void unparkSuccessor(Node node) { // 获取当前node节点的waitStatus状态 int ws = node.waitStatus; if (ws < 0)// -1:SIGNAL  // 改成0的原因:因为当前节点已经完成唤醒后驱节点线程的任务了~ compareAndSetWaitStatus(node, ws, 0); // s是当前节点的第一个后驱节点 Node s = node.next; // 条件1:s == null // s 什么时候为null? // 1.当前节点就是tail节点时,s==null // 2.当前节点入队未完成时(1.设置新节点的prev指向pred  2.CAS设置新节点为tail  3.(未完成)pred.next -> 新节点) // 需要找到可以被唤醒的节点... // 条件2:s.waitStatus > 0 前提是 s == null // 如果条件2成立,则说明当前node节点的后继节点是取消状态,需要找一个合适的可以被唤醒的节点... 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; // 上面的循环,会找到一个离当前node最近的一个可以被唤醒的节点,该节点可能找不到,可能是null } // 如果找到合适的,且可以被唤醒的节点s,则将其挂起,如果没找到,则什么也不做~ if (s != null) LockSupport.unpark(s.thread);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

上一篇文章:AQS源码探究_03 成员方法解析(加锁、资源竞争逻辑) 和 本篇文章的前面部分,都是在介绍ReentrantLock的lock()加锁方式,这种加锁方式是不可以被响应中断的,下面我们分析可以被响应中断的加锁方式lockInterruptibly()

扩展:AQS成员方法解析(响应中断加锁逻辑)

1. lockInterruptibly可以被响应中断的加锁方法

// 位于ReentrantLock中:可以被响应中断的加锁方法
public void lockInterruptibly() throws InterruptedException { // 可以被响应中断的方式去竞争资源~ sync.acquireInterruptibly(1);
}

// 位于AQS的Sync静态内部类中:竞争资源的方法(可以被响应中断)
public final void acquireInterruptibly(int arg) throws InterruptedException { // 如果当前线程已经是有中断标记interrupted为true了,则直接抛出中断异常~ if (Thread.interrupted()) throw new InterruptedException(); // 尝试获取锁 if (!tryAcquire(arg)) doAcquireInterruptibly(arg);
}

private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) // 我们主要来分析下cancelAcquire这个方法: 取消指定node参与竞争。 cancelAcquire(node); }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

2. cancelAcquire取消指定node参与竞争的方法

// 位于AQS下
/**
 * 取消指定node参与竞争。
 */
private void cancelAcquire(Node node) { //空判断.. if (node == null) return; //因为已经取消排队了..所以node内部关联的当前线程,置为Null就好了。。 node.thread = null; //获取当前取消排队node的前驱。 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; //拿到前驱的后继节点。 //1.当前node //2.可能也是 ws > 0 的节点。 Node predNext = pred.next; //将当前node状态设置为 取消状态  1 node.waitStatus = Node.CANCELLED; /** * 当前取消排队的node所在 队列的位置不同,执行的出队策略是不一样的,一共分为三种情况: * 1.当前node是队尾  tail -> node * 2.当前node 不是 head.next 节点,也不是 tail * 3.当前node 是 head.next节点。 */ //条件一:node == tail  成立:当前node是队尾  tail -> node //条件二:compareAndSetTail(node, pred) 成功的话,说明修改tail完成。 if (node == tail && compareAndSetTail(node, pred)) { //修改pred.next -> null. 完成node出队。 compareAndSetNext(pred, predNext, null); } else { //保存节点 状态.. int ws; //第二种情况:当前node 不是 head.next 节点,也不是 tail //条件一:pred != head 成立, 说明当前node 不是 head.next 节点,也不是 tail //条件二: ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) //条件2.1:(ws = pred.waitStatus) == Node.SIGNAL   成立:说明node的前驱状态是 Signal 状态   不成立:前驱状态可能是 // 极端情况下:前驱也取消排队了.. //条件2.2:(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)) // 假设前驱状态是 <= 0 则设置前驱状态为 Signal状态..表示要唤醒后继节点。 //if里面做的事情,就是让pred.next -> node.next  ,所以需要保证pred节点状态为 Signal状态。 if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { //情况2:当前node 不是 head.next 节点,也不是 tail //出队:pred.next -> node.next 节点后,当node.next节点 被唤醒后 //调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点 //完成真正出队。 Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { //当前node 是 head.next节点。  更迷了... //类似情况2,后继节点唤醒后,会调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点 //队列的第三个节点 会 直接 与 head 建立 双重指向的关系: //head.next -> 第三个node  中间就是被出队的head.next 第三个node.prev -> head unparkSuccessor(node); } node.next = node; // help GC }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

3. parkAndCheckInterruptpark当前线程方法

//AQS#parkAndCheckInterrupt
//park当前线程 将当前线程 挂起,唤醒后返回当前线程是否为中断信号唤醒。
private final boolean parkAndCheckInterrupt() { // 挂起当前线程 LockSupport.park(this); // 返回当前线程的中断标识 return Thread.interrupted();
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

小结

下面总结一下前面几篇文章的主要内容:

(1)AQS是Java中几乎所有锁和同步器的一个基础框架,这里说的是“几乎”,因为有极个别确实没有通过AQS来实现;

(2)AQS中维护了一个队列,这个队列使用双链表实现,用于保存等待锁排队的线程;

(3)AQS中维护了一个状态变量,控制这个状态变量就可以实现加锁解锁操作了;

(4)基于AQS自己动手写一个锁非常简单,只需要实现AQS的几个方法即可。

文章来源: csp1999.blog.csdn.net,作者:兴趣使然の草帽路飞,版权归原作者所有,如需转载,请联系作者。

原文链接:csp1999.blog.csdn.net/article/details/116376070

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。