Java并发之AQS原理剖析

本文主要介绍了数据结构中同步等待队列的相关内容,包括基本属性、内部类,详细解析了主要方法,如标记节点模式、获取和释放锁的不同模式方法,以及节点入队、设置尾结点、阻塞获取锁等操作,还说明了判断线程是否排队等逻辑。

数据结构:
基本属性:
复制代码
/**

  • 同步等待队列的头结点
    */
    private transient volatile Node head;

/**

  • 同步等待队列的尾结点
    */
    private transient volatile Node tail;

/**

  • 同步资源状态
    /
    private volatile int state;
    复制代码
    内部类:
    复制代码
    static final class Node {
    /
    *

    • 标记节点为共享模式
      /
      static final Node SHARED = new Node();
      /
      *
    • 标记节点为独占模式
      */
      static final Node EXCLUSIVE = null;

    static final int CANCELLED = 1;
    static final int SIGNAL = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;

    /**

    • CANCELLED: 值为1,表示当前的线程被取消
    • SIGNAL: 值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
    • CONDITION: 值为-2,表示当前节点在等待condition,也就是在condition队列中;
    • PROPAGATE: 值为-3,表示当前场景下后续的acquireShared能够得以执行;
    • 0: 表示当前节点在sync队列中,等待着获取锁。
    • 表示当前节点的状态值
      */
      volatile int waitStatus;

    /**

    • 前置节点
      */
      volatile Node prev;

    /**

    • 后继节点
      */
      volatile Node next;

    /**

    • 节点同步状态的线程
      */
      volatile Thread thread;

    /**

    • 存储condition队列中的后继节点
      */
      Node nextWaiter;

    /**

    • 是否为共享模式
      */
      final boolean isShared() {
      return nextWaiter == SHARED;
      }

    /**

    • 获取前驱结点
      */
      final Node predecessor() throws NullPointerException {
      Node p = prev;
      if (p == null)
      throw new NullPointerException();
      else
      return p;
      }

    Node() { // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) { // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
    this.waitStatus = waitStatus;
    this.thread = thread;
    }
    }
    复制代码
    主要方法解析:

tryAcquire/tryAcquireShared(int arg)
  独占/共享模式获取锁;由子类实现,仅仅获取锁,获取锁失败时不进行阻塞排队。

tryRelease/tryReleaseShared(int arg)
  独占/共享模式释放锁;由子类实现,仅仅释放锁,释放锁成功不对后继节点进行唤醒操作。

acquire/acquireShared(int arg)
  独占/共享模式获取锁,如果线程被中断唤醒,会返回线程中断状态,不会抛异常中止执行操作(忽略中断)。

acquireInterruptibly/acquireSharedInterruptibly(int arg)
  独占/共享模式获取锁,线程如果被中断唤醒,则抛出InterruptedException异常(中断即中止)。

tryAcquireNanos/tryAcquireSharedNanos(int arg, long nanosTimeout)
  独占/共享时间中断模式获取锁,线程如果被中断唤醒,则抛出InterruptedException异常(中断即中止);如果超出等待时间则返回加锁失败。

release/releaseShared(int arg)
  独占/共享模式释放锁。

addWaiter(Node mode)
  将给定模式节点进行入队操作。

复制代码
1 private Node addWaiter(Node mode) {
2 // 根据指定模式,新建一个当前节点的对象
3 Node node = new Node(Thread.currentThread(), mode);
4 // Try the fast path of enq; backup to full enq on failure
5 Node pred = tail;
6 if (pred != null) {
7 // 将当前节点的前置节点指向之前的尾结点
8 node.prev = pred;
9 // 将当前等待的节点设置为尾结点(原子操作)
10 if (compareAndSetTail(pred, node)) {
11 // 之前尾结点的后继节点设置为当前等待的节点
12 pred.next = node;
13 return node;
14 }
15 }
16 enq(node);
17 return node;
18 }
复制代码
enq(final Node node)
  将节点设置为尾结点。注意这里会进行自旋操作,确保节点设置成功。因为等待的线程需要被唤醒操作;如果操作失败,当前节点没有与其他节点没有引用指向关系,一直就不会被唤醒(除非程序代码中断线程)。

复制代码
1 private Node enq(final Node node) {
2 for (;😉 {
3 Node t = tail;
4 // 判断尾结点是否为空,尾结点初始值是为空
5 if (t == null) { // Must initialize
6 // 尾结点为空,需要初始化
7 if (compareAndSetHead(new Node()))
8 tail = head;
9 } else {
10 // 设置当前节点设置为尾结点
11 node.prev = t;
12 if (compareAndSetTail(t, node)) {
13 t.next = node;
14 return t;
15 }
16 }
17 }
18 }
复制代码
acquireQueued(final Node node, int arg)
  已经在队列当中的节点,准备阻塞获取锁。在阻塞前会判断前置节点是否为头结点,如果为头结点;这时会尝试获取下锁(因为这时头结点有可能会释放锁)。

复制代码
1 final boolean acquireQueued(final Node node, int arg) {
2 boolean failed = true;
3 try {
4 boolean interrupted = false;
5 for (;😉 {
6 // 当前节点的前置节点
7 final Node p = node.predecessor();
8 // 入队前会先判断下该节点的前置节点是否是头节点(此时头结点有可能会释放锁);然后尝试去抢锁
9 // 在非公平锁场景下有可能会抢锁失败,这时候会继续往下执行 阻塞线程
10 if (p == head && tryAcquire(arg)) {
11 //如果抢到锁,将头节点后移(也就是将该节点设置为头结点)
12 setHead(node);
13 p.next = null; // help GC
14 failed = false;
15 return interrupted;
16 }
17 // 如果前置节点不是头结点,或者当前节点抢锁失败;通过shouldParkAfterFailedAcquire判断是否应该阻塞
18 // 当前置节点的状态为SIGNAL=-1,才可以安全被parkAndCheckInterrupt阻塞线程
19 if (shouldParkAfterFailedAcquire(p, node) &&
20 parkAndCheckInterrupt())
21 // 该线程已被中断
22 interrupted = true;
23 }
24 } finally {
25 if (failed)
26 cancelAcquire(node);
27 }
28 }
复制代码
shouldParkAfterFailedAcquire(Node pred, Node node)
  检查和更新未能获取锁节点的状态,返回是否可以被安全阻塞。

复制代码
1 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
2 int ws = pred.waitStatus; // 获取前置节点的状态
3 if (ws == Node.SIGNAL)
4 /*
5 * 前置节点的状态waitStatus为SIGNAL=-1,当前线程可以安全的阻塞
6 /
7 return true;
8 if (ws > 0) {
9 /

10 * 如果前置节点的状态waitStatus>0,即waitStatus为CANCELLED=1(无效节点),需要从同步状态队列中取消等待(移除队列)
11 /
12 do {
13 node.prev = pred = pred.prev;
14 } while (pred.waitStatus > 0);
15 pred.next = node;
16 } else {
17 /

18 * 将前置状态的waitStatus修改为SIGNAL=-1,然后当前节点才可以被安全的阻塞
19 */
20 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
21 }
22 return false;
23 }
复制代码
parkAndCheckInterrupt()
  阻塞当前节点,返回当前线程的中断状态。

1 private final boolean parkAndCheckInterrupt() {
2 LockSupport.park(this); //阻塞
3 return Thread.interrupted();
4 }
cancelAcquire(Node node)
  取消进行的获取锁操作,在非忽略中断模式下,线程被中断唤醒抛异常时会调用该方法。

复制代码
1 // 将当前节点的状态设置为CANCELLED,无效的节点,同时移除队列
2 private void cancelAcquire(Node node) {
3 if (node == null)
4 return;
5
6 node.thread = null;
7 Node pred = node.prev;
8 while (pred.waitStatus > 0)
9 node.prev = pred = pred.prev;
10
11 Node predNext = pred.next;
12 node.waitStatus = Node.CANCELLED;
13 if (node == tail && compareAndSetTail(node, pred)) {
14 compareAndSetNext(pred, predNext, null);
15 } else {
16 int ws;
17 if (pred != head &&
18 ((ws = pred.waitStatus) == Node.SIGNAL ||
19 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
20 pred.thread != null) {
21 Node next = node.next;
22 if (next != null && next.waitStatus <= 0)
23 compareAndSetNext(pred, predNext, next);
24 } else {
25 unparkSuccessor(node);
26 }
27
28 node.next = node; // help GC
29 }
30 }
复制代码
hasQueuedPredecessors()
  判断当前线程是否应该排队。

1.第一种结果——返回true:(1.1和1.2同时存在,1.2.1和1.2.2有一个存在)

1.1 h != t为true,说明头结点和尾结点不相等,表示队列中至少有两个不同节点存在,至少有一点不为null。

1.2 ((s = h.next) == null || s.thread != Thread.currentThread())为true

1.2.1 (s = h.next) == null为true,表示头结点之后没有后续节点。

1.2.2 (s = h.next) == null为false,s.thread != Thread.currentThread()为true
        表示头结点之后有后续节点,但是头节点的下一个节点不是当前线程

2.第二种结果——返回false,无需排队。(2.1和2.2有一个存在)

2.1 h != t为false,即h == t;表示h和t同时为null或者h和t是同一个节点,无后续节点。

2.2 h != t为true,((s = h.next) == null || s.thread != Thread.currentThread())为false

表示队列中至少有两个不同节点存在,同时持有锁的线程为当前线程。

复制代码
1 public final boolean hasQueuedPredecessors() {
2 Node t = tail; // Read fields in reverse initialization order
3 Node h = head;
4 Node s;
5 return h != t &&
6 ((s = h.next) == null || s.thread != Thread.currentThread());
7 }
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
亚马逊测评 www.yisuping.cn
深圳网站建设www.sz886.com

下载方式:https://pan.quark.cn/s/a4b39357ea24 布线问题(分支限界算法)是计算机科学和电子工程领域中一个广为人知的议题,它主要探讨如何在印刷电路板上定位两个节点间最短的连接路径。 在这一议题中,电路板被构建为一个包含 n×m 个方格的矩阵,每个方格能够被界定为可通行或不可通行,其核心任务是定位从初始点到最终点的最短路径。 分支限界算法是处理布线问题的一种常用策略。 该算法与回溯法有相似之处,但存在差异,分支限界法仅需获取满足约束条件的一个最优路径,并按照广度优先或最小成本优先的原则来探索解空间树。 树 T 被构建为子集树或排列树,在探索过程中,每个节点仅被赋予一次成为扩展节点的机会,且会一次性生成其全部子节点。 针对布线问题的解决,队列式分支限界法可以被采用。 从起始位置 a 出发,将其设定为首个扩展节点,并将与该扩展节点相邻且可通行的方格加入至活跃节点队列中,将这些方格标记为 1,即从起始方格 a 到这些方格的距离为 1。 随后,从活跃节点队列中提取队首节点作为下一个扩展节点,并将与当前扩展节点相邻且未标记的方格标记为 2,随后将这些方格存入活跃节点队列。 这一过程将持续进行,直至算法探测到目标方格 b 或活跃节点队列为空。 在实现上述算法时,必须定义一个类 Position 来表征电路板上方格的位置,其成员 row 和 col 分别指示方格所在的行和列。 在方格位置上,布线能够沿右、下、左、上四个方向展开。 这四个方向的移动分别被记为 0、1、2、3。 下述表格中,offset[i].row 和 offset[i].col(i=0,1,2,3)分别提供了沿这四个方向前进 1 步相对于当前方格的相对位移。 在 Java 编程语言中,可以使用二维数组...
源码来自:https://pan.quark.cn/s/a4b39357ea24 在VC++开发过程中,对话框(CDialog)作为典型的用户界面组件,承担着与用户进行信息交互的重要角色。 在VS2008SP1的开发环境中,常常需要满足为对话框配置个性化背景图片的需求,以此来优化用户的操作体验。 本案例将系统性地阐述在CDialog框架下如何达成这一功能。 首先,需要在资源设计工具中构建一个新的对话框资源。 具体操作是在Visual Studio平台中,进入资源视图(Resource View)界面,定位到对话框(Dialog)分支,通过右键选择“插入对话框”(Insert Dialog)选项。 完成对话框内控件的布局设计后,对对话框资源进行保存。 随后,将着手进行背景图片的载入工作。 通常有两种主要的技术路径:1. **运用位图控件(CStatic)**:在对话框界面中嵌入一个CStatic控件,并将其属性设置为BST_OWNERDRAW,从而具备自主控制绘制过程的权限。 在对话框的类定义中,需要重写OnPaint()函数,负责调用图片资源并借助CDC对象将其渲染到对话框表面。 此外,必须合理处理WM_CTLCOLORSTATIC消息,确保背景图片的展示不会受到其他界面元素的干扰。 ```cppvoid CMyDialog::OnPaint(){ CPaintDC dc(this); // 生成设备上下文对象 CBitmap bitmap; bitmap.LoadBitmap(IDC_BITMAP_BACKGROUND); // 获取背景图片资源 CDC memDC; memDC.CreateCompatibleDC(&dc); CBitmap* pOldBitmap = m...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值