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

目录
  • 前言
  • AQS是什么?
  • 用银行办理业务的案例模拟AQS如何进行线程管理和通知机制
  • 结语

前言

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

AQS是什么?

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

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

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

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

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

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

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

部分源码附上:

	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上即可。

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

	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

	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回收。

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

结语

AQS的核心大致是这样,如果说我讲的你还是没有听懂的话,可以去B站看善硅谷阳哥的高频面试题那集去看看。我只是将其做了一个简化,用自己的话把这个过程复述出来,希望能对你有所帮助。

到此这篇关于AQS(AbstractQueuedSynchronizer)抽象队列同步器的文章就介绍到这了,更多相关AQS抽象队列同步器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java面试必备之AQS阻塞队列和条件队列

    一.AQS入队规则 我们仔细分析一下AQS是如何维护阻塞队列的,在独占方式获取资源的时候,是怎么将竞争锁失败的线程丢到阻塞队列中的呢? 我们看看acquire方法,这里首先会调用子类实现的tryAcquire方法尝试修改state,修改失败的话,说明线程竞争锁失败,于是会走到后面的这个条件: 这个addWaiter方法就是将当前线程封装成一个Node.EXCLUSIVE类型的节点,然后丢到阻塞队列中: 第一次还没有阻塞队列的时候,会到enq方法里面,我们仔细看看enq方法 enq()方法中,我们

  • Java并发编程之JUC并发核心AQS同步队列原理剖析

    目录 一.AQS介绍 二.AQS中的队列 1.同步等待队列 2.条件等待队列 3.AQS队列节点Node 三.同步队列源码分析 1.同步队列分析 2.同步队列--独占模式源码分析 3.同步队列--共享模式源码分析 一.AQS介绍 队列同步器AbstractQueuedSynchronizer(简称AQS),AQS定义了一套多线程访问共享资源的同步器框架,是用来构建锁或者其他同步组件的基础框架,是一个依赖状态(state)的同步器.Java并发编程的核心在java.util.concurrent(

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

    目录 前言 AQS是什么? 用银行办理业务的案例模拟AQS如何进行线程管理和通知机制 结语 前言 AQS 绝对是JUC的重要基石,也是面试中经常被问到的,所以我们要搞清楚这个AQS到底是什么?骑工作原理是什么? AQS是什么? AQS,AbstractQueuedSynchronizer,即队列同步器.它是构建锁或者其他同步组件的基础框架(如ReentrantLock.ReentrantReadWriteLock.Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大

  • Python 虚拟环境工作原理解析

    目录 简介 使用 激活脚本 工作原理 关于 sys.prefix 总结 其它 Python 的虚拟环境用来创建一个相对独立的执行环境,尤其是一些依赖的三方包,最常见的如不同项目依赖同一个但是不同版本的三方包,而且,在虚拟环境中的安装包不会影响到系统的安装包. 不过,其具体的工作原理是怎样的,这里详细介绍. 简介 几乎每个语言都包含自己的包管理工具,这是一个非常复杂的话题,而不同语言选择的实现又略有区别,都会做一些选择和取舍.而 Python 的包管理解决方案很多,例如 pip.virtualen

  • react  Suspense工作原理解析

    目录 Suspense 基本应用 Suspense 原理 基本流程 源码解读 - primary 组件 源码解读 - 异常捕获 源码解读 - 添加 promise 回调 源码解读-Suspense 首次渲染 primary 组件加载完成前的渲染 primary 组件加载完成时的渲染 利用 Suspense 自己实现数据加载 Suspense 基本应用 Suspense 目前在 react 中一般配合 lazy 使用,当有一些组件需要动态加载(例如各种插件)时可以利用 lazy 方法来完成.其中

  • Pinia介绍及工作原理解析

    目录 什么是Pinia 如何使用Pinia 安装 创建store 在组件中使用store 在模板中使用store Pinia是如何工作的 什么是Pinia Pinia是Vue 3的状态管理库,它提供了一种简单.可靠和可扩展的方法来管理应用程序状态.它的目标是提供一个清晰的API,易于使用,并避免不必要的性能开销. Pinia与Vuex类似,但是它采用了更现代的API和一些更好的实践.Pinia将状态分为两类:响应式状态和非响应式状态.响应式状态是指可以在Vue组件中使用的状态,而非响应式状态是指

  • Android Handler工作原理解析

    简介 在Android 中,只有主线程才能操作 UI,但是主线程不能进行耗时操作,否则会阻塞线程,产生 ANR 异常,所以常常把耗时操作放到其它子线程进行.如果在子线程中需要更新 UI,一般是通过 Handler 发送消息,主线程接受消息并且进行相应的逻辑处理.除了直接使用 Handler,还可以通过 View 的 post 方法以及 Activity 的 runOnUiThread 方法来更新 UI,它们内部也是利用了Handler .在上一篇文章 Android AsyncTask源码分析

  • Beego AutoRouter工作原理解析

    目录 一.前言 二.从一个例子入手 AutoRouter的解析规则: 三.AutoRouter是如何工作的 结语 一.前言 Beego Web框架应该是国内Go语言社区第一个框架,个人觉得十分适合新手入门Go Web.笔者半年前写过一篇搭建Beego项目并实习简单功能的文章,大家有兴趣可以先看看. 其实我接触的大部分人都在学校学过Java Web,其实有Java Web的经验,上手Beego也会很舒服. 本文着重讲讲Beego的AutoRouter模块,会结合源码来讲讲,不过由于笔者技术水平有限

  • java同步器AQS架构AbstractQueuedSynchronizer原理解析

    目录 引导语 1.整体架构 1.1.类注释 1.2.类定义 1.3.基本属性 1.3.1.简单属性 1.3.2.同步队列属性 1.3.3.条件队列的属性 1.3.4.Node 1.3.5.共享锁和排它锁的区别 1.4.Condition 2.同步器的状态 3.获取锁 3.1.acquire排它锁 3.1.1.addWaiter 3.1.2.acquireQueued 3.2.acquireShared获取共享锁 4.总结 引导语 AbstractQueuedSynchronizer 中文翻译叫做

  • java同步器AQS架构AbstractQueuedSynchronizer原理解析下

    目录 引导语 1.释放锁 1.1.释放排它锁release 1.2.释放共享锁releaseShared 2.条件队列的重要方法 2.1.入队列等待await 2.1.1.addConditionWaiter 2.1.2.unlinkCancelledWaiters 2.2.单个唤醒signal 2.3.全部唤醒signalAll 3.总结 引导语 AQS 的内容太多,所以我们分成了两个章节,没有看过 AQS 上半章节的同学可以回首看一下哈,上半章节里面说了很多锁的基本概念,基本属性,如何获得锁

  • 浅谈Android中AsyncTask的工作原理

    概述 实际上,AsyncTask内部是封装了Thread和Handler.虽然AsyncTask很方便的执行后台任务,以及在主线程上更新UI,但是,AsyncTask并不合适进行特别耗时的后台操作,对于特别耗时的任务,个人还是建议使用线程池.好了,话不多说了,我们先看看AsyncTask的简单用法吧. AsyncTask使用方法 AsyncTask是一个抽象的泛型类.简单的介绍一下它的使用方式代码如下: package com.example.huangjialin.myapplication;

  • 详解JSP 中Spring工作原理及其作用

    详解JSP 中Spring工作原理及其作用 1.springmvc请所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责负责对请求进行真正的处理工作. 2.DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller. 3.DispatcherServlet请请求提交到目标Controller 4.Controller进行业务逻辑处理后,会返回一个ModelAndView 5.Dispathcher查询一个或多个

随机推荐