JDK源码:JUC之AbstractQueuedSynchronizer(AQS)
发布日期:2021-06-30 21:30:21
浏览次数:2
分类:技术文章
本文共 13345 字,大约阅读时间需要 44 分钟。
目录
AQS是什么
AbstractQueuedSynchronizer类顾名思义:抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,最常用的如ReentrantLock。
我们先看AbstractQueuedSynchronizer源码分析其原理和提供的功能,再结合ReentrantLock来进行使用分析。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { private static final long serialVersionUID = 7373984972572414691L;...}
首先AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer抽象类,该类只提供一个功能,在独占锁的模式下设置独占线程。
/** * 可以由线程以独占方式拥有的同步器。 * 此类为创建锁和相关同步器(伴随着所有权的概念)提供了基础。 * AbstractOwnableSynchronizer类本身不管理或使用此信息。 * 但是,子类和工具可以使用适当维护的值帮助控制和监视访问以及提供诊断。 * * @since 1.6 * @author Doug Lea */public abstract class AbstractOwnableSynchronizer implements java.io.Serializable { /** 必须使用序列化 */ private static final long serialVersionUID = 3737899427754241961L; /** * 子类调用的控构造函数. */ protected AbstractOwnableSynchronizer() { } /** * 独占状态下 */ private transient Thread exclusiveOwnerThread; /** * 设置当前所有者线程 */ protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; } /** * 返回最后一次调用setExclusiveOwnerThread的线程,没有则为null */ protected final Thread getExclusiveOwnerThread() { return exclusiveOwnerThread; }}
Node:队列中存储线程的节点类
/*** 等待队列中存储线程的节点类/* 等待队列是“CLH”锁队列。 CLH锁即Craig, Landin, and Hagersten (CLH) locks(根据三个人名定义的名称)。 CLH锁通常用于自旋锁,我们用它们来阻止同步器。 CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。 CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。 但使用相同的基本策略,即在其节点的前一个线程中保存一些有关该线程的控制信息。 每个节点中的“status”字段跟踪线程是否应该阻塞,当一个节点的前一个节点释放时,它会发出信号 否则,队列的每个节点都充当一个特定的通知样式监视器,其中包含一个等待线程。 但是,status字段不控制线程是否被授予锁等。 如果线程是队列中的第一个线程,则它可能会尝试获取。但是,第一并不保证成功,它只给予竞争的权利因此,当前发布的竞争者线程可能需要重写。 要排队进入clh锁,需要将其作为新的尾巴要出列,只需设置head字段。 插入CLH队列只需要对“tail”执行一个原子操作,所以有一个简单的原子点,从未排队到排队 类似地,出列只涉及更新“head”。 但是,节点需要做更多的工作来确定其继任者是谁,部分原因是为了处理由于超时和中断可能导致的取消。“prev”链接(不在原始clh锁中使用),主要用于处理取消。 如果节点被取消,则其后续节点(通常)将重新链接到未取消的前置节点。 对于自旋锁的类似力学解释,见Scott和Scherer的论文:http://www.cs.rochester.edu/u/Scott/synchronization/ 我们还使用“next”链接来实现阻塞机制,每个节点的线程id都保存在自己的节点中, 因此,前置任务通过遍历下一个链接来确定它是哪个线程,从而向下一个节点发出唤醒信号, 后续节点的确定必须避免与新排队的节点竞争,以设置其前置节点的“next”字段。 必要时,当节点的后继节点看起来为空时,可以通过从原子更新的“tail”向后检查来解决此问题。(或者,换言之,下一个链接是一个优化,因此我们通常不需要反向扫描。) 取消的基本算法有一些保守性, 因为我们必须轮询其他节点的取消,所以我们可能会忽略已取消的节点是在我们前面还是后面,这是由取消时总是断开继承人的连接来处理的,允许他们稳定在一个新的前任,除非我们能确定将承担这一责任的前任。 clh队列需要一个虚拟头节点才能启动,但我们不能再构造器里创建, 因为如果没有竞争这个将没用。相反,节点是构造的,并且在第一次竞争时设置头指针和尾指针。 等待条件的线程使用相同的节点, 但是使用一个附加的链接。 条件只需要链接简单(非并发)链接队列中的节点,因为它们只在独占持有时才被访问。 等待时,节点被插入到条件队列中 一旦发出信号,节点就被转移到主队列。 状态字段的特殊值用于标记节点所在的队列。*/*/static final class Node { /** 共享模式标记 */ static final Node SHARED = new Node(); /** 独享模式标记 */ static final Node EXCLUSIVE = null; /** 等待状态为1,表示线程已取消 */ static final int CANCELLED = 1; /** 等待状态为-1,表示后续线程需要断开连接 */ static final int SIGNAL = -1; /** 等待状态为-2,表示线程正在等待 */ static final int CONDITION = -2; /** * 等待状态为-3,表示当前场景下后续的acquireShared可以被执行,共享锁。 */ static final int PROPAGATE = -3; /** * 状态字段 * */ volatile int waitStatus; /** * 前驱节点,比如当前节点被取消,那就需要前驱节点和后继节点来完成连接。 */ volatile Node prev; /** * 后续节点 */ volatile Node next; /** * 使用当前节点的线程,构造十进行初始化,使用后为null */ volatile Thread thread; /** * 存储condition队列中的后继节点 */ Node nextWaiter; /** * 如果是共享模式下等待,这返回true */ final boolean isShared() { return nextWaiter == SHARED; } /** * 返回上一个节点,如果为空,则抛出NullPointerException。 * 前置任务不能为空时使用。 * 空检查可以被省略,但是可以帮助VM。 * * @return 前置节点 */ final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() { // 用于建立初始头部或共享标记 } Node(Thread thread, Node mode) { // addWaiter方法使用,创建节点模式,独占或共享 this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { // Condition方法使用,创建一个等待节点 this.waitStatus = waitStatus; this.thread = thread; } }
head、tail、state:头尾节点和同步状态
/** * 等待队列的头,延迟初始化。 * 除了初始化,它只通过方法setHead进行修改。 * 注意:如果头存在,保证它的状态不被取消。 */ private transient volatile Node head; /** * 等待队列的尾部,延迟初始化。 * 仅通过方法enq修改以添加新的等待节点。 */ private transient volatile Node tail; /** * 同步状态. */ private volatile int state; /** * 返回当前状态值 * @return 当前状态 */ protected final int getState() { return state; } /** * 设置同步状态 * @param 新状态 */ protected final void setState(int newState) { state = newState; } /** * 原子性设置同步状态 * * @param 修改前的值 * @param 修改后的值 * @return 修改成功返回ture,否者false */ protected final boolean compareAndSetState(int expect, int update) { //底层原子性设置 return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
enq(final Node node):队列处理方法
/** * 将节点插入队列 * @param node 要插入的节点 * @return 节点前置节点 */ private Node enq(final Node node) { for (;;) { //获取尾部节点 Node t = tail; if (t == null) { //为空则初始化节点,并设置首尾相同 if (compareAndSetHead(new Node())) tail = head; } else { //设置尾部节点为当前节点的前置节点,并设置尾部节点为当前节点。 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
addWaiter(Node mode):创建模式节点(独享、共享)
/** * 为当前线程创建节点,并给定模式,独享或共享 * * @param Node.EXCLUSIVE 独享, Node.SHARED 共享 * @return 新的节点 */ private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); //直接插入队列末尾,不成功者使用常规的enq方法创建。 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
setHead(Node node):刷新头部节点
/** * 设置节点为首节点 * 并清空当前队列和前置节点,为了GC回收已处理的信息,并避免不必要的遍历 * * @param node the node */ private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }
unparkSuccessor(Node node):唤醒后续节点
/** * 唤醒节点的后继节点,如果存在的话 * * @param 当前节点 */ private void unparkSuccessor(Node node) { /* * 如果当前状态为负数,变更状态 */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * 如果后续节点为空,或状态大于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; } //后续节点不为空则进行unpark唤醒操作 if (s != null) LockSupport.unpark(s.thread); }
LockSupport工具类用于对线程进行堵塞或唤醒,包括下面4个方法
- void park():阻塞当前线程,如果掉用unpark(Thread)方法或被中断,才能从park()返回
- void parkNanos(long nanos):阻塞当前线程,超时返回,阻塞时间最长不超过nanos纳秒
- void parkUntil(long deadline):阻塞当前线程,直到deadline时间点
- void unpark(Thread):唤醒处于阻塞状态的线程
doReleaseShared():共享模式下释放锁
/** * 共享模式下的释放操作,通知后续操作并确保传播 * (注意:独占模式下如果需要是否信号,使用release相当于调用head的unparkSuccessor。) */ 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; } }
setHeadAndPropagate(Node node,int propagate):设置队列的头部节点并传播
/** * 设置队列的头部节点并传播 * @param 需要设置为头部节点的节点 * @param 传播从tryAcquireShared方法返回的值 */ private void setHeadAndPropagate(Node node, int propagate) { Node h = head; //记录旧头部节点 setHead(node); //设置头部节点 /* * 当传播信号大于0即取消,或旧头结点为空,或旧头节点状态为负值, * 或新节点为空,新节点装为负数时,则判断下一个节点 */ if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; //下一个节点为空或在共享等待状态只执行释放共享资源 if (s == null || s.isShared()) doReleaseShared(); } }
cancelAcquire(Node node):取消节点获取资源的尝试
/** * 取消节点获取资源的尝试 * * @param 尝试获取中的节点 */ private void cancelAcquire(Node node) { // 不为空校验 if (node == null) return; node.thread = null; //获取前置节点 Node pred = node.prev; //找到非取消的前置节点 while (pred.waitStatus > 0) node.prev = pred = pred.prev; // predNext is the apparent node to unsplice. CASes below will // fail if not, in which case, we lost race vs another cancel // or signal, so no further action is necessary. //获取前置节点的下一个节点 Node predNext = pred.next; // Can use unconditional write instead of CAS here. // After this atomic step, other Nodes can skip past us. // Before, we are free of interference from other threads. //设置节点状态为取消 node.waitStatus = Node.CANCELLED; // If we are the tail, remove ourselves. // 如果当前节点为尾部节点,则当前节点替换成前置节点 if (node == tail && compareAndSetTail(node, pred)) { //将下一个节点设置为null compareAndSetNext(pred, predNext, null); } else { //前置节点不为头部节点,且为共享模式,不为共享变更为共享,并且节点线程不能为空 int ws; if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; //修改前置节点的后续节点为取消节点的后续节点 if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { //当前节点释放资源 unparkSuccessor(node); } //设置当前节点后续节点为当前节点,提醒gc回收 node.next = node; } }
shouldParkAfterFailedAcquire(Node pred, Node node):循环尝试获取信号
/** * 检查并更新无法获取信号的节点状态。 * 如果前置节点已获取信号,则当前节点应该阻塞,返回true。 * 过程中删除已取消节点 * 这个方法是所有获取信号的主要控制方法 * @param pred 前置节点 * @param node 当前节点 * @return {@code true} */ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) //当前节点已获取信号则直接返回true 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); } return false; }
selfInterrupt():中断当前线程
/** * 终端当前线程 */ static void selfInterrupt() { Thread.currentThread().interrupt(); }
parkAndCheckInterrupt():堵塞当前线程,并返回是否已中断
/** * 堵塞当前线程,并返回是否已中断 * * @return {@code true} 中断状态 */ private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
各种各样获取锁的方式
/** * 用于循环获取等待 * queue. Used by condition wait methods as well as acquire. * * @param node the node * @param arg the acquire argument * @return {@code true} if interrupted while waiting */ 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; //返回flase,表示当前线程没有中断 return interrupted; } //刷新队列并中断当前节点 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { //如果当前节点为中断状态,则取消重试 if (failed) cancelAcquire(node); } }
转载地址:https://lizz6.blog.csdn.net/article/details/102926675 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
感谢大佬
[***.8.128.20]2024年04月30日 00时22分44秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
送一个闲置显示器!
2019-04-30
Oracle 行转列 pivot函数基本用法
2019-04-30
Oracle字符串分隔符替换(替换奇数个或偶数个)
2019-04-30
Oracle 利用 UTL_SMTP 包发送邮件
2019-04-30
Oracle 的循环中的异常捕捉和处理
2019-04-30
Oracle通过pivot和unpivot配合实现行列转换
2019-04-30
给Oracle数据库换一个1522端口的监听
2019-04-30
Excel表格数据生成ECharts图表
2019-04-30
阿里云短信服务python版,pyinstaller打包运行时缺少文件
2019-04-30
Oracle的pfile和spfile的一点理解和笔记
2019-04-30
WebService的简单案例记录(Java)
2019-04-30
Html利用PHP与MySQL交互
2019-04-30
dos简单命令
2019-04-30
mysql的安装与卸载与Navicat远程连接
2019-04-30
java实现稀疏数组及将稀疏数组存入硬盘中
2019-04-30
2021-05-18
2019-04-30
Flutter 使用插件打开相册、相机
2019-04-30
libuv实现tcp代理服务器
2019-04-30
libuv使用不当导致的内存泄漏
2019-04-30