AQS源码分析

编程入门 行业动态 更新时间:2024-10-13 04:18:29

AQS<a href=https://www.elefans.com/category/jswz/34/1770099.html style=源码分析"/>

AQS源码分析

文章目录

      • 1) 同步队列
      • 2) 独占同步状态的获取与释放

1) 同步队列

同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

下面直接呈上源代码!!!字多看的头疼

同步队列中的节点(Node):

static final class Node {/** waitStatus value to indicate 线程被中断或者超时 */static final int CANCELLED =  1;/** waitStatus value to 按时后继节点可以运行 */static final int SIGNAL    = -1;/** waitStatus value to 节点在等待队列(WAIT),等待被唤醒后加入同步队列(BLOCK) */static final int CONDITION = -2;/*** waitStatus value to indicate the next acquireShared should* unconditionally propagate*/static final int PROPAGATE = -3;// 等待状态如上所示volatile int waitStatus;// 前驱节点volatile Node prev;// 后继volatile Node next;// 获取同步状态的线程volatile Thread thread;// 等待队列中的后继节点Node nextWaiter;final boolean isShared() {return nextWaiter == SHARED;}// 获取节点的前继final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}Node() {    // Used to establish initial head or SHARED marker}Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}
}

节点是构成同步队列,如下图所示 太懒直接上原图!

注意:当线程无法获取同步状态时,需要将线程转换为Node,转存同步队列,由于加入过程需要是线程安全,所以加入CAS操作来设置尾节点,代码如下:

private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// tail为同步队列的尾节点Node pred = tail;if (pred != null) {node.prev = pred;// 快速尝试在尾部添加,如果CAS不存在,使用死循环的方式进行添加节点if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 如果尾结点为nullenq(node);return node;
}private Node enq(final Node node) {// “死循环”来保证节点的正确添加,for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}

过程见下图:

同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。

private void unparkSuccessor(Node node) {/*获取节点状态*/int ws = node.waitStatus;// 如果小于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);}

2) 独占同步状态的获取与释放

  • 获取

    通过调用同步器的acquire(int arg)方法可以获取同步状态,该方法对中断不敏感,也就是由于线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出

    代码

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
    }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 GCfailed = false;return interrupted;}// 查看是否被中断,中断后仍然在同步队列中保存。这也是为什么不能直接使用头节点的后继的原因				了if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
    }
    

    流程

  1. 调用自定义同步器实现的tryAcquire(int arg)方法,同步状态获取失败执行(2)。
  2. 构造同步节点(设置独占式Node.EXCLUSIVE)并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部。
  3. 调用acquireQueued(Node node,int arg)方法,使得该节点以“死循环”的方式获取同步状态。

  • 释放

    当前线程获取同步状态并执行了相应逻辑之后,就需要释放同步状态。通过调用同步器的release(int arg)方法可以释放同步状态,该方法在释放了同步状态之后,会唤醒其后继节点。

    代码

    public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;// 唤醒后继节点if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
    }
    

    next: 独占式超时获取同步状态, 共享式同步状态获取与释放

更多推荐

AQS源码分析

本文发布于:2024-03-05 19:48:01,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1713240.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:源码   AQS

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!