【JavaEE】AQS原理

news2024/12/29 10:44:17

本文将介绍AQS的简单原理。


首先有个整体认识,全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。常用的ReentrantLock、Semaphore、CountDownLatch等都有实现它。

本文参考:

深入理解AbstractQueuedSynchronizer只需15张图_abstractqueuedsynchronizer流程图-CSDN博客

 黑马程序员深入学习Java并发编程,JUC并发编程全套教程_哔哩哔哩_bilibili

从ReentrantLock的实现看AQS的原理及应用 - 美团技术团队 (meituan.com)

Java AQS 核心数据结构-CLH 锁 (qq.com)


AQS关键组成部分


state

state用来表示资源的状态(独占模式和共享模式),子类需要控制这个变量,就能够获取锁或者释放锁。

独占模式:只允许一个线程访问资源。

共享模式:允许多个线程访问资源。


CLH队列

这个队列保存的是没有获得锁资源进入阻塞的线程。使用的是头节点和尾结点来创建出这个双向队列。这个队列是先进先出的,不支持优先级。这里的CLH队列是AQS对于CLH锁的升级改后的应用。

该队列是对自旋锁的改进。那么自旋锁有上面缺点呢?

自旋锁


public class SpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<Thread>();

    public void lock() {
        Thread currentThread = Thread.currentThread();
        // 如果锁未被占用,则设置当前线程为锁的拥有者
        // 这里就是自旋锁的核心部分
        while (!owner.compareAndSet(null, currentThread)) {
        }
    }

    public void unlock() {
        Thread currentThread = Thread.currentThread();
        // 只有锁的拥有者才能释放锁
        owner.compareAndSet(currentThread, null);
    }
}

第一个是锁饥饿问题。在锁竞争激烈的情况下,可能存在一个线程一直被其他线程”插队“而一直获取不到锁的情况。

第二是性能问题。在实际的多处理上运行的自旋锁在锁竞争激烈时性能较差。

CLH队列就把上述的两个问题解决了。

CLH队列过程(加解锁)

  1. 初始化一个 Tail 指向一个状态为false的空节点。
  2. 当t1线程获取锁,tail就指向t1线程,同时修改状态为true
  3. 当t2也想获得锁,此时t1还没释放,tail就指向t2,同时t1对应的节点。此时t2检查到t1节点为false,就开始轮询上一个节点的状态
  4. 当t1结束后,就把值修改为false
  5. 当t2轮询到值为false后,就是获取锁成功。

AQS升级CLH锁

  1. 扩展了每个节点的状态(waitStatus),从简单的true和false,扩展到如下部分。
  2. 维护前驱后继节点
    原始版本的 CLH 锁中,节点间甚至都没有互相链接。但是,通过在节点中显式地维护前驱节点,CLH 锁就可以处理“超时”和各种形式的“取消”:如果一个节点的前驱节点取消了,这个节点就可以滑动去使用前面一个节点的状态字段。
  3. 用阻塞等待代替自选操作
    当前一个节点释放锁之后,它会通知后一个节点获取锁。

条件变量

如果线程不满足条件变量后,那么它就要进入另外一种队列进行等待。每一个条件变量都会创建出一个队列。其中这个队列是单向的。


锁资源获取与释放

  • 独占式
    • acquire获取资源
    • release释放资源
  • 共享式
    • acquireShared获取资源
    • releaseShared释放资源

获得独占锁

acquire是个模板函数,模板流程就是线程获取共享资源,如果获取资源成功,线程直接返回,否则进入CLH队列,直到获取资源成功为止,且整个过程忽略中断的影响。

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

释放独占锁

AQS中提供了release模板函数来释放资源,模板流程就是线程释放资源成功,唤醒CLH队列的第二个线程节点。

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

获得共享锁

acquireShared是个模板函数,模板流程就是线程获取共享资源,如果获取到资源,线程直接返回,否则进入CLH队列,直到获取到资源为止,且整个过程忽略中断的影响。

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

释放共享锁

AQS中提供了releaseShared模板函数来释放资源,模板流程就是线程释放资源成功,唤醒CHL队列的第二个线程节点(首节点的下个节点)

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

自定义锁

下面我们自定义一个锁,该锁内部的同步器是不可重入的独占锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

// 自定义锁
public class MyLock implements Lock {

    // 独占锁 同步器类
    public class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int acquires) {

            if (compareAndSetState(0, 1)) {
                // 加锁,并设置成当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        @Override
        protected boolean tryRelease(int acquires) {
            setExclusiveOwnerThread(null);
            // state是被volatile修饰的,exclusiveOwnerThread 没有被volatile修饰
            // 所以设置exclusiveOwnerThread成null要放在前面
            setState(0);
            return true;
        }
        protected Condition newCondition() {
            return new ConditionObject();
        }

        // 是否持有独占锁
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

    private MySync sync = new MySync();

    // 加锁,不成功进入等待队列
    @Override
    public void lock() {
        sync.acquire(1);
    }

    // 加锁,不成功进入等待队列,可被中断
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    // 尝试加锁一次,不成功直接返回
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    // 尝试加锁(带超时的),不成功进入等待队列,可被中断
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    // 解锁
    @Override
    public void unlock() {
        // 这里release本质上调用的是tryRelease方法 解锁并唤醒等待队列中的线程
        // 如果调用的是release方法,就不会唤醒等待的线程
        sync.release(1);
    }

    // 创建一个Condition
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

class Test {
    public static void main(String[] args) {
        MyLock lock = new MyLock();
        new Thread(() -> {
            lock.lock();
            long now = System.currentTimeMillis();
            try {
                System.out.println("t1 获取锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("t1 释放锁");
                System.out.println("t1 耗时:" + (System.currentTimeMillis() - now));
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("t2 获取锁");
            } finally {
                System.out.println("t2 释放锁");
                lock.unlock();
            }
        }, "t2").start();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2.5.LeNet

1.LeNet ​ LeNet-5由两个部分组成: 卷积编码器&#xff1a;由两个卷积层组成全连接层密集块&#xff1a;由三个全连接层组成 ​ 先试用卷积层来学习图片空间信息&#xff0c;然后使用全连接层来转换到类别空间 ​ 第一层卷积层要padding一下&#xff0c;收集边框的信息&…

数据清洗系统设计

设计一个高效的数据清洗系统旨在确保数据的质量&#xff0c;以便后续分析和决策过程可以基于准确、一致和完整的信息。以下是设计实时数据清洗系统时需要考虑的关键要素&#xff0c;结合之前提到的设计目标和原则&#xff1a; 1. 高效的数据处理 技术选型&#xff1a;采用并行…

git遇到OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 0

最简单的方法&#xff0c;直接忽略SSL证书错误就好 一般是代理http/https或者其他问题导致的 直接输入 git config --global http.sslVerify "false" 即可

数学建模学习(2)——决策树

import pandas as pd from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score dfpd.read_excel(股票客户流失.xlsx) xdf.drop(columns是否流失)#x等于除是否流失这一列以外的数据…

layui+thymeleaf+jquery实现多图片,多视频的上传、预览、放大、编辑功能

layuithymeleafjquery实现多图片&#xff0c;多视频的上传、预览、放大、编辑功能 html: <!--多图片上传--> <div class"layui-row layui-col-space10"><div class"layui-form-item"><div class"layui-form-item layui-form-te…

证书上的服务器名错误解决方法

方法 win r &#xff0c;输入mmc 点击文件——>添加/删除管理单元 找到证书——> 添加 根据自己的存放选择存放位置 点击控制台根节点——> 受信任的根证书颁发机构——>导入 若还出现问题&#xff0c;则参考https://blog.csdn.net/mm120138687/article/details/…

【BUG】已解决:The above exception was the direct cause of the following exception:

The above exception was the direct cause of the following exception: 目录 The above exception was the direct cause of the following exception: 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c…

uniapp中出现Uncaught runtime errors

项目中运行出现上面的错误信息&#xff0c;使用uniapp发现&#xff0c;其实我只是跨域了&#xff0c;控制台报错&#xff0c;但是不想屏幕上显示&#xff1b; 解决办法是在vue.config.js增加如下配置即可 devServer: {client: {overlay: false,errors:true},}, 错误信息也不想…

求职学习day8

7/21回顾&#xff1a; 用面试鸭的意义可能就在于将知识点用问答的形式具象化在脑海&#xff0c;不然可能只停留在听说过的感觉 7.21 玩了一天。一个很不好的信号。今天下午要试试把 mall 项目的代码运行过一遍。 项目运行问题&#xff1a; 问题 1 &#xff1a;两个门服务器…

Modbus转BACnet/IP网关的技术实现与应用

引言 随着智能建筑和工业自动化的快速发展&#xff0c;不同通信协议之间的数据交换也变得日益重要。Modbus和BACnet/IP是两种广泛应用于自动化领域的通信协议&#xff0c;Modbus以其简单性和灵活性被广泛用于工业自动化&#xff0c;而BACnet/IP则在楼宇自动化系统中占据主导地…

昇思25天学习打卡营第18天| DCGAN生成漫画头像

DCGAN&#xff0c;全称深度卷积对抗生成网络&#xff08;Deep Convolutional Generative Adversarial Networks&#xff09;&#xff0c;是一种通过对抗训练生成图像的技术。它在判别器和生成器中都使用了卷积和转置卷积层。 训练分为两个部分&#xff1a;训练判别器和训练生成…

在spyder中使用arcgis pro的包

历时2天终于搞定了 目标&#xff1a;在anconda中新建一个arcpyPro环境&#xff0c;配置arcgispro3.0中的arcpy 一、安装arcgispro3.0 如果安装完之后打开arcgispro3.0闪退&#xff0c;就去修改注册表&#xff08;在另一台电脑安装arcgispro遇到过&#xff09; 安装成功后可…

【影刀】自动化办公介绍与RPA机器人实例

影刀介绍 影刀RPA是杭州分叉智能科技有限公司开发的一款自动化办公软件。 它是基于Machine Behavior Learning(机器行为学习)技术&#xff0c;为各行业提供行为自动化办公机器人。 影刀能做什么&#xff1f; 有逻辑、规则的工作都能完成操作。 影刀RPA可以在任何应用程式上…

K8S 上部署 Prometheus + Grafana

文章目录 一、使用 Helm 安装 Prometheus1. 配置源2. 下载 prometheus 包3. 安装 prometheus4. 卸载 二、使用 Helm 安装 Grafana1. 配置源2. 安装 grafana3. 访问4. 卸载 一、使用 Helm 安装 Prometheus 1. 配置源 地址&#xff1a;https://artifacthub.io/packages/helm/pro…

Nginx 如何实现请求的缓存过期策略?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; 文章目录 Nginx 如何实现请求的缓存过期策略&#xff1f;一、缓存的重要性与基本概念二、Nginx 缓存过期策略的原理三、设置 Nginx 缓存过期时间四、基于变量的动态缓存过…

rv1126利用rkmedia、opencv、rockx……完成人脸识别

一、总体框架 视频采集、处理使用rkmedia&#xff1a;vi模块进行视频输入、rga模块进行视频处理 人脸识别&#xff1a;先获取rga输出码流&#xff0c;再调用rkmedia的模型对人脸进行推理&#xff08;线程1&#xff09; 打框框&#xff1a;opencv&#xff08;线程2&#xff0…

go-kratos 学习笔记(2) 创建api

proto 声明SayHi 先删除go.mod 从新初始化一下 go mod init xgs_kratosgo mod tidy 编辑 api/helloword/v1/greeter.proto 新声明一个方法 rpc SayHi (HelloHiRequest) returns (HelloHiReply) {option (google.api.http) {post: "/hi"body: "*"};} …

Leetcode之string

目录 前言1. 字符串相加2. 仅仅反转字母3. 字符串中的第一个唯一字符4. 字符串最后一个单词的长度5. 验证回文串6. 反转字符串Ⅱ7. 反转字符串的单词Ⅲ8. 字符串相乘9. 打印日期 前言 本篇整理了一些关于string类题目的练习, 希望能够学以巩固. 博客主页: 酷酷学!!! 点击关注…

llama 2 改进之 RMSNorm

RMSNorm 论文&#xff1a;https://openreview.net/pdf?idSygkZ3MTJE Github&#xff1a;https://github.com/bzhangGo/rmsnorm?tabreadme-ov-file 论文假设LayerNorm中的重新居中不变性是可有可无的&#xff0c;并提出了均方根层归一化(RMSNorm)。RMSNorm根据均方根(RMS)将…

DolphinScheduler安装教程

DolphinScheduler安装教程 前期准备工作 jdk 1.8mysql 5zookeeper 3.4.6hadoop 2.6psmisc yum -y install psmisc 解压安装包 # 将安装包apache-dolphinscheduler-2.0.8-bin.tar.gz放置/opt/download目录下 # 解压缩 tar -zxvf apache-dolphinscheduler-2.0.8-bin.tar.gz -C …