iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > node.js >什么是AQS抽象队列同步器
  • 924
分享到

什么是AQS抽象队列同步器

2024-04-02 19:04:59 924人浏览 八月长安
摘要

这篇文章主要介绍“什么是AQS抽象队列同步器”,在日常操作中,相信很多人在什么是AQS抽象队列同步器问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是AQS抽象队列同步器”

这篇文章主要介绍“什么是AQS抽象队列同步器”,在日常操作中,相信很多人在什么是AQS抽象队列同步器问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是AQS抽象队列同步器”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

抽象队列同步器(AQS-AbstractQueuedSynchronizer)

从名字上来理解:

  • 抽象:是抽象类,具体由子类实现

  • 队列:数据结构是队列,使用队列存储数据

  • 同步:基于它可以实现同步功能

我们就从这几个方面来入手解读,但首先,我们得先知道以下几个它的特点,以便于理解

AbstractQueuedSynchronizer特点

1.AQS可以实现独占和共享锁。

2.独占锁exclusive是一个悲观锁。保证只有一个线程经过一个阻塞点,只有一个线程可以获得锁。

3.共享锁shared是一个乐观锁。可以允许多个线程阻塞点,可以多个线程同时获取到锁。它允许一个资源可以被多个读操作,或者被一个写操作访问,但是两个操作不能同时访问。

4.AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state =  0无锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int  update))来对同步状态state进行操作,可以确保对state的操作是安全的。

5.AQS是通过一个CLH队列实现的(CLH锁即Craig, Landin, and Hagersten (CLH)  locks,CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。)

抽象

我们来扒一扒源码可以看到它继承于AbstractOwnableSynchronizer它是一个抽象类.

public abstract class AbstractQueuedSynchronizer     extends AbstractOwnableSynchronizer     implements java.io.Serializable

AQS内部使用了一个volatile的变量state来作为资源的标识。同时定义了几个获取和改变state的protected方法,子类可以覆盖这些方法来实现自己的逻辑.

可以看到类中为我们提供了几个protected级别的方法,它们分别是:

//创建一个队列同步器实例,初始state是0 protected AbstractQueuedSynchronizer() { }  //返回同步状态的当前值。 protected final int getState() {         return state; }  //设置同步状态的值 protected final void setState(int newState) {         state = newState;     }  //独占方式。尝试获取资源,成功则返回true,失败则返回false。 protected boolean tryAcquire(int arg) {         throw new UnsupportedOperationException();     }               //独占方式。尝试释放资源,成功则返回true,失败则返回false。 protected boolean tryRelease(int arg) {         throw new UnsupportedOperationException();     }           //共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源 protected int tryAcquireShared(int arg) {         throw new UnsupportedOperationException();     }               //共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。 protected boolean tryReleaseShared(int arg) {         throw new UnsupportedOperationException();     }

这些方法虽然都是protected方法,但是它们并没有在AQS具体实现,而是直接抛出异常,AQS实现了一系列主要的逻辑  由此可知,AQS是一个抽象的用于构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的同步器,比如我们提到的ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。

我们自己也能利用AQS非常轻松容易地构造出自定义的同步器,只要子类实现它的几个protected方法就可以了.

队列

AQS类本身实现的是具体线程等待队列的维护(如获取资源失败入队/唤醒出队等)。它内部使用了一个先进先出(FIFO)的双端队列(CLH),并使用了两个指针head和tail用于标识队列的头部和尾部。其数据结构如图:

什么是AQS抽象队列同步器

队列并不是直接储存线程,而是储存拥有线程的node节点。

我们来看看Node的结构:

static final class Node {     // 标记一个结点(对应的线程)在共享模式下等待     static final Node SHARED = new Node();     // 标记一个结点(对应的线程)在独占模式下等待     static final Node EXCLUSIVE = null;       // waitStatus的值,表示该结点(对应的线程)已被取消     static final int CANCELLED = 1;      // waitStatus的值,表示后继结点(对应的线程)需要被唤醒     static final int SIGNAL = -1;     // waitStatus的值,表示该结点(对应的线程)在等待某一条件     static final int CONDITION = -2;          //waitStatus的值,表示有资源可用,新head结点需要继续唤醒后继结点     //(共享模式下,多线程并发释放资源,而head唤醒其后继结点后,     //需要把多出来的资源留给后面的结点;设置新的head结点时,会继续唤醒其后继结点)     static final int PROPAGATE = -3;      // 等待状态,取值范围,-3,-2,-1,0,1     volatile int waitStatus;     volatile Node prev; // 前驱结点     volatile Node next; // 后继结点     volatile Thread thread; // 结点对应的线程     Node nextWaiter; // 等待队列里下一个等待条件的结点       // 判断共享模式的方法     final boolean isshared() {         return nextWaiter == SHARED;     }      Node(Thread thread, Node mode) {     // Used by addWaiter         this.nextWaiter = mode;         this.thread = thread;     }      // 其它方法忽略,可以参考具体的源码 }  // AQS里面的addWaiter私有方法 private Node addWaiter(Node mode) {     // 使用了Node的这个构造函数     Node node = new Node(Thread.currentThread(), mode);     // 其它代码省略 }

过Node我们可以实现两个队列,一是通过prev和next实现CLH队列(线程同步队列,双向队列),二是nextWaiter实现Condition条件上的等待线程队列(单向队列),这个Condition主要用在ReentrantLock类中

同步

两种同步方式:

  • 独占模式(Exclusive):资源是独占的,一次只能一个线程获取。如ReentrantLock。

  • 共享模式(Share):同时可以被多个线程获取,具体的资源个数可以通过参数指定。如Semaphore/CountDownLatch。

同时实现两种模式的同步类,如ReadWriteLock

获取资源

获取资源的入口是acquire(int arg)方法。arg是要获取的资源的个数,在独占模式下始终为1。

public final void acquire(int arg) {     if (!tryAcquire(arg) &&         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))         selfInterrupt(); }

首先调用tryAcquire(arg)尝试去获取资源。前面提到了这个方法是在子类具体实现的如果获取资源失败,就通过addWaiter(Node.EXCLUSIVE)方法把这个线程插入到等待队列中。其中传入的参数代表要插入的Node是独占式的。这个方法的具体实现:

private Node addWaiter(Node mode) {     // 生成该线程对应的Node节点     Node node = new Node(Thread.currentThread(), mode);     // 将Node插入队列中     Node pred = tail;     if (pred != null) {         node.prev = pred;         // 使用CAS尝试,如果成功就返回         if (compareAndSetTail(pred, node)) {             pred.next = node;             return node;         }     }     // 如果等待队列为空或者上述CAS失败,再自旋CAS插入     enq(node);     return node; }  //AQS中会存在多个线程同时争夺资源的情况, //因此肯定会出现多个线程同时插入节点的操作, //在这里是通过CAS自旋的方式保证了操作的线程安全性。  // 自旋CAS插入等待队列 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;             }         }     } }

若设置成功就代表自己获取到了锁,返回true。状态为0设置1的动作在外部就有做过一次,内部再一次做只是提升概率,而且这样的操作相对锁来讲不占开销。如果状态不是0,则判定当前线程是否为排它锁的Owner,如果是Owner则尝试将状态增加acquires(也就是增加1),如果这个状态值越界,则会抛出异常提示,若没有越界,将状态设置进去后返回true(实现了类似于偏向的功能,可重入,但是无需进一步征用)。如果状态不是0,且自身不是owner,则返回false。

现在通过addWaiter方法,已经把一个Node放到等待队列尾部了。而处于等待队列的结点是从头结点一个一个去获取资源的。具体的实现我们来看看acquireQueued方法:

final boolean acquireQueued(final Node node, int arg) {     boolean failed = true;     try {         boolean interrupted = false;         // 自旋         for (;;) {             final Node p = node.predecessor();             // 如果node的前驱结点p是head,表示node是第二个结点,就可以尝试去获取资源了             if (p == head && tryAcquire(arg)) {                 // 拿到资源后,将head指向该结点。                 // 所以head所指的结点,就是当前获取到资源的那个结点或null。                 setHead(node);                  p.next = null; // help GC                 failed = false;                 return interrupted;             }             // 如果自己可以休息了,就进入waiting状态,直到被unpark()             if (shouldParkAfterFailedAcquire(p, node) &&                 parkAndCheckInterrupt())                 interrupted = true;         }     } finally {         if (failed)             cancelAcquire(node);     } }

这里parkAndCheckInterrupt方法内部使用到了LockSupport.park(this),顺便简单介绍一下park。LockSupport类是Java  6  引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:park(boolean  isAbsolute, long time):阻塞当前线程 unpark(Thread jthread):使给定的线程停止阻塞

所以结点进入等待队列后,是调用park使它进入阻塞状态的。只有头结点的线程是处于活跃状态的。

acquire方法 获取资源的流程:

什么是AQS抽象队列同步器

当然,获取资源的方法除了acquire外,还有以下三个:

  • acquireInterruptibly:申请可中断的资源(独占模式)

  • acquireShared:申请共享模式的资源

  • acquireSharedInterruptibly:申请可中断的资源(共享模式)

可中断的意思是,在线程中断时可能会抛出InterruptedException

释放资源

释放资源相比于获取资源来说,会简单许多。在AQS中只有一小段实现。

源码:

public final boolean release(int arg) {     if (tryRelease(arg)) {         Node h = head;         if (h != null && h.waitStatus != 0)             unparkSuccessor(h);         return true;     }     return false; }

tryRelease方法  这个动作可以认为就是一个设置锁状态的操作,而且是将状态减掉传入的参数值(参数是1),如果结果状态为0,就将排它锁的Owner设置为null,以使得其它的线程有机会进行执行。

在排它锁中,加锁的时候状态会增加1(当然可以自己修改这个值),在解锁的时候减掉1,同一个锁,在可以重入后,可能会被叠加为2、3、4这些值,只有unlock()的次数与lock()的次数对应才会将Owner线程设置为空,而且也只有这种情况下才会返回true。这一点大家写代码要注意,如果是在循环体中lock()或故意使用两次以上的lock(),而最终只有一次unlock(),最终可能无法释放锁。导致死锁.

private void unparkSuccessor(Node node) {     // 如果状态是负数,尝试把它设置为0     int ws = node.waitStatus;     if (ws < 0)         compareAndSetWaitStatus(node, ws, 0);     // 得到头结点的后继结点head.next     Node s = node.next;     // 如果这个后继结点为空或者状态大于0     // 通过前面的定义我们知道,大于0只有一种可能,就是这个结点已被取消     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); }

方法unparkSuccessor(Node),意味着真正要释放锁了,它传入的是head节点,内部首先会发生的动作是获取head节点的next节点,如果获取到的节点不为空,则直接通过:“LockSupport.unpark()”方法来释放对应的被挂起的线程.

到此,关于“什么是AQS抽象队列同步器”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: 什么是AQS抽象队列同步器

本文链接: https://www.lsjlt.com/news/83146.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 什么是AQS抽象队列同步器
    这篇文章主要介绍“什么是AQS抽象队列同步器”,在日常操作中,相信很多人在什么是AQS抽象队列同步器问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是AQS抽象队列同步器”...
    99+
    2024-04-02
  • AQS(AbstractQueuedSynchronizer)抽象队列同步器及工作原理解析
    目录前言AQS是什么?用银行办理业务的案例模拟AQS如何进行线程管理和通知机制结语前言 AQS 绝对是JUC的重要基石,也是面试中经常被问到的,所以我们要搞清楚这个AQS到底是什么?...
    99+
    2024-04-02
  • java同步器AQS架构如何释放锁和同步队列
    这篇文章主要为大家展示了“java同步器AQS架构如何释放锁和同步队列”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“java同步器AQS架构如何释放锁和同步队列”这篇文章吧。引导语AQS 的内容...
    99+
    2023-06-29
  • Java中的AQS同步队列问题详解
    目录AQS 同步队列1、AQS 介绍1.1、类图关系1.2、节点剖析2、AQS 实现原理2.1、队列初始化2.2、追加节点3、AQS 唤醒动作AQS 同步队列 1、AQS 介绍 AQ...
    99+
    2024-04-02
  • Java中的AQS同步队列问题怎么解决
    这篇文章主要介绍“Java中的AQS同步队列问题怎么解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java中的AQS同步队列问题怎么解决”文章能帮助大家解决问题。AQS 同步队列1、AQS 介绍...
    99+
    2023-07-02
  • java同步器AQS架构AbstractQueuedSynchronizer原理是什么
    这篇文章主要介绍“java同步器AQS架构AbstractQueuedSynchronizer原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java同步器AQS架构AbstractQueu...
    99+
    2023-06-29
  • 如何深入理解Java多线程与并发框中的队列同步器AQS
    如何深入理解Java多线程与并发框中的队列同步器AQS,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、 AbstractOwnableSynchronizer 抽象的、可...
    99+
    2023-06-05
  • Java并发编程之JUC并发核心AQS同步队列原理剖析
    目录一、AQS介绍二、AQS中的队列1、同步等待队列2、条件等待队列3、AQS队列节点Node三、同步队列源码分析1、同步队列分析2、同步队列——独占模式源码分析3、同步队列——共享...
    99+
    2024-04-02
  • redis异步队列是什么意思
    redis异步队列是指将队列里的东西进行异步处理,异步即是主动请求数据后便可以继续处理其它任务,随后等待IO操作完毕的通知,得到通知之后,再去选择对这些数据做操作。...
    99+
    2024-04-02
  • 什么是php抽象类和抽象方法
    这篇文章主要介绍“什么是php抽象类和抽象方法”,在日常操作中,相信很多人在什么是php抽象类和抽象方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是php抽象类和抽象方法”的疑惑有所帮助!接下来,请跟...
    99+
    2023-06-20
  • java中抽象类和接口的相同和不同点是什么
    这篇文章将为大家详细讲解有关java中抽象类和接口的相同和不同点是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。前言下面简单介绍抽象类,接口以及它们的异同点,另附简单的代码举例。一、抽象...
    99+
    2023-06-22
  • 什么是消息队列
    这期内容当中小编将会给大家带来有关什么是消息队列,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。很多人在做架构设计时往往会“过度设计”,简单问题复杂化,上来就引一堆中间件,...
    99+
    2024-04-02
  • 数据抽象中有三个抽象级别是什么
    这篇文章给大家分享的是有关数据抽象中有三个抽象级别是什么的内容。小编觉得挺实用的,因此分享给大家做个参考。一起跟随小编过来看看吧。          &nb...
    99+
    2024-04-02
  • java抽象方法是什么
    Java中的抽象方法是一种没有具体实现的方法,只有方法的声明而没有方法体。抽象方法必须在抽象类中定义,且抽象类必须用abstract...
    99+
    2023-08-29
    java
  • kettle增量同步抽取数据的方法是什么
    kettle增量同步抽取数据的方法通常是通过以下步骤实现:1. 确定增量字段:首先需要确定用于判断数据是否已经同步的增量字段。这个字...
    99+
    2023-09-20
    kettle
  • 什么是抽象工厂模式
    这篇文章主要介绍“什么是抽象工厂模式”,在日常操作中,相信很多人在什么是抽象工厂模式问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是抽象工厂模式”的疑惑有所帮助!接下来,...
    99+
    2024-04-02
  • MySQL优先队列是什么
    本篇内容介绍了“MySQL优先队列是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! 0.先抛...
    99+
    2024-04-02
  • VB.NET消息队列是什么
    这篇文章主要介绍VB.NET消息队列是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!消息队列是 Windows 2000(NT也有MSMQ,WIN95/98/ME/XP不含消息队列服务但是支持客户端的运行)操作系...
    99+
    2023-06-17
  • php抽象类指的是什么
    这篇文章主要介绍了php抽象类指的是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇php抽象类指的是什么文章都会有所收获,下面我们一起来看看吧。PHP抽象类指的是至少拥有一个抽象方法的类;抽象类不能被实例化...
    99+
    2023-07-02
  • redis消息队列是什么
    redis消息队列是什么?这个问题可能是我们日常学习或工作经常见到的。希望通过这个问题能让你收获颇深。下面是小编给大家带来的参考内容,让我们一起来看看吧!队列是一种特殊的线性表,特殊之处在于它只允许在表的前...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作