• AQS核心原理分析《上》


    前言:大家好,我是小威,24届毕业生,在一家满意的公司实习。本篇文章是关于并发编程中AQS的简介和组成知识记录,由于篇幅原因,独占模式和共享模式将会在下篇记录。
    本篇文章记录的基础知识,适合在学Java的小白,也适合复习中,面试中的大佬🤩🤩。
    如果文章有什么需要改进的地方还请大佬不吝赐教👏👏。
    小威在此先感谢各位大佬啦~~🤞🤞
    在这里插入图片描述

    🏠个人主页:小威要向诸佬学习呀
    🧑个人简介:大家好,我是小威,一个想要与大家共同进步的男人😉😉
    目前状况🎉:24届毕业生,在一家满意的公司实习👏👏

    🎁如果大佬在准备面试,可以使用我找实习前用的刷题神器哦刷题神器点这里哟
    💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘

    以下正文开始

    在这里插入图片描述

    AQS简介

    AQS位于java.util.concurrent.locks包下,其全称是AbstractQueuedSynchronizer,即抽象队列同步器,是阻塞式锁和相关的同步器工具的框架。

    在AQS内部,主要维护了一个基于FIFO(First Input First Output)的等待队列,类似于前面讲到的monitor锁的WaitSet集合。
    在这里插入图片描述

    AQS底层数据结构

    同时,双向链表中的每个节点都是对线程的封装,对于每个节点,都分别指向各自的直接前驱节点和直接的后继节点。当然,也可以对任意一个节点进行遍历。

    在多线程并发情况下,当线程竞争锁失败后,会被封装成一个Node节点,加入到AQS队列的末尾,当当前获取锁的对象释放锁后,会在AQS队列中唤醒一个被阻塞的线程

    当然,在AQS队列中,还维护了一个被volatile关键字修饰的变量,它的名字叫做state,state,在中文中有状态的意思,因此state,记录了每个线程的状态。当然,其对应有getState方法–获取state状态,setState方法–设置state状态。多线程情况下,可想而知,肯定会出现线程安全问题,但是,but,设置state状态时,会用到CAS乐观锁机制,调用unsafe类中的方法,保证线程的安全,提供了compareAndSetState方法保证了多线程修改state变量的原子性,(CAS乐观锁在前面文章中记录过)如下面的源码:

        //使用volatile关键字修饰
        private volatile int state;
        //getState方法
        protected final int getState() {
            return state;
        }
        //setState方法
        protected final void setState(int newState) {
            state = newState;
        }
        //compareAndSetState方法
        protected final boolean compareAndSetState(int expect, int update) {
            // See below for intrinsics setup to support this
            return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    回想Synchronized锁中,有WaitSet和EntryList集合,而在AQS中,当然也有条件变量队列,即condition队列。条件变量可以来实现等待,唤醒机制,不同的是,AQS条件变量中,支持多个条件变量。而在synchronized锁中,使用notifyAll()方法,会一次性将所有阻塞的线程全部唤醒。

    AQS对底层锁的支持(Node类)

    AQS支持独占锁和共享锁两种模式。独占锁是同时只有一个线程能够访问资源,如基于AQS实现的Reentrantlock锁(这个知识后面会详细讲解)。而共享锁可以同时允许多个线程访问资源

    而独占锁和共享锁模式都是在静态内部类Node中定义的,去掉注释后,Node类的源码如下(包括注释):

    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;
            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();
                else
                    return p;
            }
            
            // Used to establish initial head or SHARED marker,无参构造
            Node() {    
            }
            
            // Used by addWaiter,创建有条件队列的节点
            Node(Thread thread, Node mode) {     
                this.nextWaiter = mode;
                this.thread = thread;
            }
            
            // Used by Condition ,带有初始waitStatus状态的节点
            Node(Thread thread, int waitStatus) { 
                this.waitStatus = waitStatus;
                this.thread = thread;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    解释:

    注意:对于AQS,阻塞队列和条件队列是不一样的。阻塞队列是采用双向链表保存的,有pre和next两个属性,而对于上文说的条件队列Condition,是用nextWaiter来链接的,表示当前节点唤醒后,依据该节点的状态,判断是以共享模式,还是以独占模式,其他条件等方式来唤醒下一个线程(节点)

    CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,表示结束状态,进入此状态后的结点将不会再变化。

    SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行其实就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行

    CONDITION:值为-2,与条件变量相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

    PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态表示当前状况下,有资源能够执行后面的acquireShared操作。

    由上面代码可知,每个线程(节点包含五个属性:prev,next,thread,waitState,nextWaiter)。其中,变量waitStatus则表示当前被封装成Node结点的等待状态,共有4种取值,默认为0CANCELLED、SIGNAL、CONDITION、PROPAGATE,表示当前线程(节点)处于sync同步队列中,等待获取锁资源

    因此,waitState状态有三种:

    1. 当waitState>0时,表示该线程处于取消状态(线程中断或者等待锁超时),需要移除线程;
    2. 当waitState=0时,默认值,表示初始化状态,表示线程还未完成初始化操作;
    3. 当waitState<0,表示有效状态,该线程处于可唤醒状态。

    阻塞队列的运行

    当有新线程插入队列时,如果插入的队列为null,插入队列后为头结点,则会直接获取资源,开始执行线程,如果不是头结点,在多线程并发情况下,前驱节点进行CAS自旋操作,直到前驱节点执行完成,waitState状态修改为CANCELLED,断开前驱节点的连接,保证原子性更新尾部的节点,获取到资源执行线程。总的来说,头部节点获取资源执行任务,后续节点通过CAS自旋操作查询前面节点是否完成执行,直到头部节点执行完自己的任务,并将waitState状态修改后,通知后续节点获取资源执行任务。
    在这里插入图片描述

    本篇文章就分享到这里了,后续将会分享各种其他关于并发编程的知识,感谢大佬认真读完支持咯 ~
    在这里插入图片描述

    文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起讨论🍻
    希望能和诸佬们一起努力,今后进入到心仪的公司
    再次感谢各位小伙伴儿们的支持🤞

    在这里插入图片描述

  • 相关阅读:
    基于SpringBoot的篮球竞赛预约平台
    图论基础 —— 概述
    JVM第三话 -- JVM性能调优实战和高频面试题记录
    MyBatis 中的 foreach 的用法
    C++学习笔记(Ⅰ):C++基础入门
    [Unity][VR]Oculus透视开发图文教程1-Passthrough应用XR项目设置
    MySQL多表查询面试题一
    解决IP地址欺骗的网络安全策略
    【C++】STL—— unordered_map的介绍和使用、 unordered_map的构造函数和迭代器、 unordered_map的增删查改函数
    C++核心编程(持续更新)
  • 原文地址:https://blog.csdn.net/qq_53847859/article/details/127608631