AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

作者:聪明不喝牛奶 时间:2023-02-24 22:20:09 

前言

AQS 绝对是JUC的重要基石,也是面试中经常被问到的,所以我们要搞清楚这个AQS到底是什么?骑工作原理是什么?

AQS是什么?

AQS,AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件,相比synchronized,synchronized缺少了获取锁与释放锁的可操作性,可中断、超时获取锁。

是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO对列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态。

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

CLH队列:CLH(Craig, Landin, and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。通过CAS完成对State值的修改。
同步对列的内部结构及继承关系

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

用银行办理业务的案例模拟AQS如何进行线程管理和通知机制

1、初始化的时候,state = 0 (0 表示没有人,1表示有人),线程池也有没有人在执行,现在就是没有顾客的时候,因此第一个线程去的时候都是公平锁状态直接到窗口办理业务。

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

2、现在有一个线程Thread A进来那么就直接到了业务窗口去办理,并且通过CAS将state的值变成1

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

3、此时有一个线程Thread B 进来,通过getStatue() 方法查看到state = 1,此时ThreadA有在占用着,所以现在ThreadB线程就必须先入队等待ThreadA结束后再调用,但是由于现在队列是空的,所以ThreadB线程并不会马上进入到队列,他会先进入addWaiter() 方法到enq()这个方法中去,这个方法实质上就是一个自旋锁,在这个方法中主要的时候先要实现一个队列的初始化工作,先形成一个傀儡结点(哨兵结点)起到一个占位的作用,然后才能将ThreadB线程挂在后面。

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

部分源码附上:

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;// 将tail的前指针赋值过去
    if (pred != null) { //一开始的时候并没有指向所以位null,因此条件不成立不走里面的语句
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {//CAS
            pred.next = node;
            return node;
        }
    }
    enq(node);//一开始会来走这个enq()这个方法。
    return node;
}

因为当前的ThreadB线程进入时,tail的prev指针为null所以就会进enq()方法。如下:

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

4、ThreadC线程开始入队,那么这个就是跟ThreadB是一样的,只不过是说没有了初始化那步了,直接挂在线程ThreadB上即可。

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

5、ThreadB 会去尝试acquireQueued()这个方法,那么第一次的时候会将哨兵结点的waitStatus = -1; 然后继续自旋,进入到if条件的下一个条件中被调用park() 阻塞掉,等待着ThreadA 线程的unpark()。这样才算真正的入列等待了。

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

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;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) //第一次的时候会阻塞在前面的条件并将哨兵的waitState置为-1,第二次的话第一个条件为真,所以会走第二个判断条件,会将访问的线程给阻塞掉等待unpark();
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); //将访问次方法的线程给阻塞掉,等待unpark()方法唤醒
    return Thread.interrupted();
}

6、当ThreadA线程办好业务的时候那么就会调用unlock()方法释放锁,unlock() 方法中又会调用sync.release()方法,并将当前窗口的线程变成null,state 置成0,通过调用 unparkSuccessor()方法将 傀儡结点的waitState = 0

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析


private void unparkSuccessor(Node node) {
       int ws = node.waitStatus;
       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); //释放锁唤醒等待队列中的等待线程
   }

7、接着会调用unpark()方法将在前面等待的ThreadB线程给唤醒去窗口办业务。将head结点指向ThreadB结点,ThreadB的prev结点为null,next结点也为null,ThreadB变成空结点,此节点就成了新的哨兵结点,原来的哨兵结点被GC回收。

AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析

8、ThreadC线程出列和上面的过程是一样的。

来源:https://blog.csdn.net/Ppphill_C/article/details/123719598

标签:AQS,抽象,队列
0
投稿

猜你喜欢

  • Android自定义View实现通讯录字母索引(仿微信通讯录)

    2023-05-14 04:34:22
  • C#之set与get方法的用法案例

    2021-08-09 01:17:18
  • OpenCV实现简单摄像头视频监控程序

    2021-12-10 01:23:07
  • SpringBoot注入自定义的配置文件的方法详解

    2021-12-24 02:28:06
  • WebView设置WebViewClient的方法

    2023-09-01 05:34:58
  • java实现输入输出流代码分享

    2023-11-18 01:03:45
  • Spring JPA find单表查询方法示例详解

    2022-11-03 19:43:38
  • RegexOptions.IgnoreCase正则表达式替换,忽略大小写

    2022-05-03 17:25:45
  • JSch教程使用sftp协议实现服务器文件载操作

    2023-10-29 17:43:33
  • SpringBoot如何优雅的整合Swagger Api自动生成文档

    2022-08-10 00:49:29
  • Qt for Android开发实例教程

    2023-06-27 10:00:39
  • 解决Android Studio4.1没有Gsonfomat插件,Plugin “GsonFormat” is incompatible的问题

    2021-08-27 22:10:37
  • 关于C#委托三种调用的分享使用

    2022-10-29 03:24:35
  • java实现倾斜水印铺满整张图

    2023-12-24 22:34:21
  • Java实现在线聊天功能

    2021-10-18 22:16:23
  • 详解Android版本适配:9.0 Pie

    2022-08-07 05:37:43
  • Kotlin 基础教程之数组容器

    2021-09-09 17:22:40
  • Java中多媒体文件上传及页面回显的操作代码

    2021-11-21 09:45:31
  • Android开发ImageView图片无法显示解决过程

    2023-06-07 21:34:39
  • Android提高之TelephonyManager功能探秘

    2021-06-11 01:19:12
  • asp之家 软件编程 m.aspxhome.com