AQS源码分析

前言

AbstractQueuedSynchronizer是抽象同步队列,其是实现同步机器的基础组件,并发包中的锁的底层就是使用AQS实现的。

AQS中 维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。
这里volatile能够保证多线程下的可见性,当state=1则代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO的等待队列中,并且会被UNSAFE.park()操作挂起,等待其他获取锁的线程释放锁才能够被唤醒。
另外state的操作都是通过CAS来保证其并发修改的安全性。

一、AQS中的关键成员变量

  • state
    • 在AQS中,维护了一个单一变量state,对于不同的实现其有不同的意义:
    • 在ReentrantLock中,state表示重入式锁的可重入次数
    • 在ReentrantReadWriteLock中,state的高16位用于表示读锁的可获取次数,低16位用于表示写锁的可重入次数。
  • exclusiveOwnerThread
    • 继承自AbstractOwnableSynchronizer,用于指明当前独占线程。
  • headtail
    • 维护了一个队列,分别指向首尾节点
  • Node
    • Node节点内部的SHARED用来标记该线程是在获取共享资源时被阻塞挂起放入AQS队列的,EXCLUSIVE用来标识该线程是获取独占资源时被阻塞挂起放入AQS队列的。
    • 在Node节点内部有一个成员变量waitStatus记录当前线程等待状态,可以为:
      • 1:CANCELLED(线程被取消了)
      • -1:SIGNAL(线程需要唤醒)
      • -2:CONDITION(线程在条件队列里等待)
      • -3:PROPAGATE(释放资源时需要通知其他节点)
  • ConditionObject
    • ConditionObject和Node一样是AQS的内部类。它用来结合锁实现线程同步,其可以访问AQS的内部变量(state和AQS阻塞队列)。
    • ConditionObject是条件变量,每个条件变量对应一个条件队列,我们可以看到ConditionObject中有两个指针,分别指向条件队列的队尾和队头。条件队列用来存放调用条件变量的await方法后被阻塞的线程。

二、线程中断相关的三个方法

三、Unsafe与LockSupport

Unsafe

  • CAS的全称是Compare-And-Swap,它是一条CPU并发原语。
  • 它的功能是判断内存某个位置是否是预期值,如果是则更改为新的值,这个过程是原子性的
  • CAS并发原语在 java的体现就是sun.mic.Unsafe类个各个方法,调用Unsafe类的方法,JVM会帮助我们实现CAS汇编指令。这是一个完全依赖于硬件的功能,通过它实现原子性操作。由于CAS是一种系统原语,由若干指令组成,该原语执行必须连续的不许中断。

这里设置了静态代码块提前获取了state、head、tail、waitStatus、next四个参数在对象内存中的偏移量。

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;
    private static final long waitStatusOffset;
    private static final long nextOffset;

    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }

    /**
     * CAS head field. Used only by enq.
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    /**
     * CAS tail field. Used only by enq.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    /**
     * CAS waitStatus field of a node.
     */
    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

    /**
     * CAS next field of a node.
     */
    private static final boolean compareAndSetNext(Node node,
                                                   Node expect,
                                                   Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }

LockSupport

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调用LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行

  • park函数,阻塞线程,并且该线程在下列情况发生之前都会被阻塞: ① 调用unpark函数,释放该线程的许可。② 该线程被中断。③ 设置的时间到了。并且,当time为绝对时间时,isAbsolute为true,否则,isAbsolute为false。当time为0时,表示无限等待,直到unpark发生。
  • unpark函数,释放线程的许可,即激活调用park后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。
public class LockSupportDemo {

    public static void main(String[] args) {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程A被LockSupport.park()阻塞");
                LockSupport.park();
                System.out.println("线程A被线程B LockSupport.unpark()唤醒");
            }
        },"A");
        A.start();

        
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程B唤醒线程A");
                // 唤醒指定线程t,也就是A
                LockSupport.unpark(A);
            }
        },"B")
        B.start();
    }
}


结果:
线程ALockSupport.park()阻塞
线程B唤醒线程A
线程A被线程B LockSupport.unpark()唤醒

四、核心源码

以ReentrantLock为例进行讲解,AQS是典型的模板方法的实现,所以AQS对外暴露了多个个抽象方法(tryAcquire、tryRelease等等)需要子类进行实现。
ReentrantLock的lock方法实际上调用了sync的lock方法,而sync继承了AQS,同时针对公平策略和非公平策略有不同的实现。这里我们主要看针对非公平锁NonfairSync的实现。

lock()

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 第一次加锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 持有锁的线程重复加锁
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    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;
        // 获取锁失败,再次判断队列是否初始化
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
    private Node enq(final Node node) {
        // 第一次执行,也就是head和tail两个指针都为null,会初始化两个Node
        for (;;) {
            Node t = tail;
            // 初始化队列,设置一个空Node,并将head与tail两个指针同时指向该节点
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            // 队列已经初始化完成,则将该节点插入队列尾部
            } else {
                node.prev = t;
                // 注意,此时t仍然指向,为尾节点的上一个节点
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }



    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 如果当前节点的前置节点是头节点,则意味着本次入队操作是第一次
                final Node p = node.predecessor();
                    // 如果是第一次入队,则再次尝试获取state
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                    // 非第一次入队/第一次入对的第二次循环
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

因为lock方法是不可中断的,所以从lock方法中进来构建起来的同步队列不会有CANCELLED状态。CONDITION用于条件队列当中。PROPAGETE是用于共享模式下的状态。

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // Acquire失败以后是否需要挂起,true:需要-false:不需要
        // 针对ReentrantLock,这里指挥判断SIGNAL
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        // ws > 0  =  CANCELLED
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }


整体流程:
image.png

unLock()

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

image.png

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            // 标识锁是否释放
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        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);
    }

这里线程二被唤醒以后将继续执行acquireQueued方法,判断线程二的前置节点是否为head,如果是则继续使用tryAcquire()方法来尝试获取锁,其实就是使用CAS操作来修改state值,如果修改成功则代表获取锁成功。接着将线程二设置为head节点,然后空置之前的head节点数据,被空置的节点数据等着被垃圾回收

在线程二释放锁以后,这个时候CLH队列中就只剩下线程三:

五、知识拓展

公平锁与非公平锁

非公平锁执行原理:

公平锁执行原理:

参考内容:

  • https://www.bilibili.com/video/BV1vM411r7Bt
  • https://juejin.cn/post/6844904146127044622

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/473735.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

监控系统prometheus+grafana+发送告警信息

1、基础环境准备两台或更多的主机 2、关闭selinux vi /etc/selinux/config&#xff0c;修改SELINUX的值为disabled 3、关闭防火墙 systemctl disable firewalld systemctl stop firewalld 4、prometheus官网下载 https://prometheus.io/download/ 5、grafana官网下载 https…

Linux-shell中变量的引用($变量名,${变量名})

1. 背景 最近写脚本时&#xff0c;发现有个变量在某个地方生效&#xff0c;某个地方又不生效&#xff0c;引用方式为 $变量名。 2. 方法 其实 shell 脚本中对变量的引用有两种方式&#xff1a; $变量名${变量名} 用下面的脚步&#xff0c;去测试效果&#xff1a; a100 b2…

专题三 - 二分 - leetcode 704. 二分查找 | 简单难度

leetcode 704. 二分查找 leetcode 704. 二分查找 | 简单难度1. 题目详情1. 原题链接2. 基础框架 2. 解题思路1. 题目分析2. 算法原理3. 时间复杂度 3. 代码实现4. 知识与收获 leetcode 704. 二分查找 | 简单难度 1. 题目详情 给定一个 n 个元素有序的&#xff08;升序&#x…

让扣你代码的人电脑关机-js反爬

文案 让扣你代码的人电脑关机&#xff0c;赶紧学起来。众所周知。浏览器中无法导入模块&#xff0c;会报错。nodejs中可以导入模块。那么我们可以在导入语句后加入整蛊代码。在捕获异常后执行正常的代码。那么代码在浏览器中就会正常执行&#xff0c;而当你在本地环境中执行的…

Pytorch CUDA Reflect Padding 算子实现详解

CUDA 简介 CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA开发的一种并行计算平台和应用编程接口&#xff08;API&#xff09;&#xff0c;允许软件开发者和软件工程师使用NVIDIA的图形处理单元&#xff08;GPU&#xff09;进行通用计算。自2007…

网页无插件视频播放器,支持录像、截图、音视频播放,多路播放等,提供源码下载

前言 本播放器内部采用jessibuca插件接口&#xff0c;支持录像、截图、音视频播放等功能。播放器播放基于ws流&#xff0c;分屏操作支持1分屏、4分屏、6分屏、9分屏方式。 jessibuca工作原理是通过Emscripten将音视频解码库编译成Js&#xff08;WebAssembly&#xff0c;简称was…

STC89C52RC单片机烧录时遇到的问题

(1)我之前安装了虚拟串口&#xff0c;跟物理串口冲突了&#xff0c;导致烧录失败。 把虚拟串口删除即可。 &#xff08;2&#xff09;我使用的是STC89C52RC单片机&#xff0c;而不是STC89C52单片机。 所以红色位置之前填错了。 (3)单片机冷启动是什么&#xff0c;难怪程序烧录…

多种排序讲解

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习多种排序&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 文章目录 冒泡排序选择排序插入排序希尔排序堆排序快速排序提醒 冒泡排序 冒泡排序就是遍历数…

vscode用SSH远程开发c语言

vscode配置远程 这里我使用虚拟机进行展示&#xff0c;首先需要你的虚拟机安装好ssh 没安装好就执行下面的命令安装并开启服务 sudo apt-get install ssh sudo service ssh start ps -e | grep sshvscode安装 remote-ssh扩展 点击左下角的远程连接&#xff0c;我这里已经连接…

51单片机学习笔记8 中断系统及定时器

51单片机学习笔记8 中断系统及定时器 一、中断的概念二、51单片机的中断1. 51单片机的中断源2. 中断的优先级3. 中断结构4. 外部中断解读5. 定时器中断6. 串口中断 三、中断相关寄存器1. IE 中断允许寄存器2. TCON 中断请求标志3. IP 中断优先级 四、中断号五、代码实现按键 &a…

如何使用 ArcGIS Pro 制作好看的高程渲染图

虽然 ArcGIS Pro 已经提供了很多好看的配色方案&#xff0c;但是如果直接对高程DEM进行渲染效果不是很理想&#xff0c;我们可以结合山体阴影让高程渲染图看起来更加立体&#xff0c;这里为大家介绍一下制作方法&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是…

【概念验证(POC):技术项目开发的关键一步】

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

g++在windows下使用C++进程库无法传入参数<求助>

如题&#xff1a; windows11使用g的时候&#xff0c;想使用下线程库。但是就发现了如题的问题。在使用时&#xff0c;不传入参数时不会报错的&#xff0c;但是传入参数之后就产生了报错。 点击进入定义发现头文件定义明明是正确的。 具体报错如下图。

3D云展平台让普通画手也能拥有自己的3D虚拟互动作品展

艺术家韩美林说过&#xff0c;我的作品都是我的孩子。 对于绘画家来说&#xff0c;每一张倾注心血和时间的画作都像自己的孩子&#xff0c;都是用心之作&#xff0c;出于某种客观或者主观原因&#xff0c;一些有才华的画家难以在线下举办画展&#xff0c;哪怕是短短几天&#x…

web表单标签加练习

表单标签 --- 行内标签 描述&#xff1a;一个完整的表单标签通常由表单域、表单控件&#xff08;表单元素&#xff09;和提示信息三部分构成 作用&#xff1a;数据交互&#xff08;C/S&#xff09; &#xff08;1&#xff09;表单域 --- <form> <form>标签用于定…

如何在尽量不损害画质的前提下降低视频占内存大小?视频格式科普及无损压缩软件推荐

大家好呀&#xff0c;相比大家都有对视频画质和体积的追求和取舍&#xff0c;那么&#xff0c;如何才能在不牺牲画质的前提下&#xff0c;尽可能的将视频大小降低到极致呢&#xff1f; 首先我们要了解视频的构成&#xff0c;要想降低视频的体积大小&#xff0c;我们可以从以下几…

Linux系统——Mysql数据库操作

目录 一、数据库基本操作 1.查看数据库结构 1.1查看数据库信息——Show databases 1.2查看数据库中的表信息——Show tables Show tables in 数据库名 use 数据库名 show tables 1.3显示数据表的结构&#xff08;字段&#xff09;——Describe&#xff08;Desc&#x…

查看angular版本的问题The Angular CLI requires a minimum Node.js version of v18.13.

angular版本与node.js版本不匹配的问题 下载安装angular 查看版本&#xff0c;发现不匹配 安装指定版本即可 查看版本并运行

网络编程-DAY6

1>创建一个武器信息库&#xff0c;包含编号&#xff08;主键&#xff09;、名称、属性、描述、价格 2>添加三把武器 3>修改某把武器的价格 4>展出价格在1000到4000的武器 5>卖掉一把武器&#xff0c;删除该武器的信息 6>几天后&#xff0c;客户顶着光头…

【Qt】使用Qt实现Web服务器(四):传递参数、表单提交和获取请求参数

1、示例 1)演示 2)提交 3)显示 2、源码 1)示例源码Demo1->FormController void FormController::service(HttpRequest& request, HttpResponse& response) {