JUC并发编程之AQS原理

news2024/11/21 2:38:15

1. AQS 原理

1.1 概述

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

特点:

  • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个生态,控制如何获取锁和释放锁
    • getState - 获取 state 状态
    • setState - 设置 state 状态
    • compareAndSetState - cas 机制设置 state 状态
    • 独占模式是只有一个线程能够访问资源,类似于 Monitor 的 EntryList
  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
  • 条件变量来实现等待、唤醒机制,支持多个条件,类似于 Monitor 的 WaitSet

子类主要实现这样一些方法 (默认抛出UnsupportedOperationException)

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

获取锁的姿势

// 如果获取锁失败
if (!tryAcquire(arg)) {
    // 入队,可以选择阻塞当前线程  park unpark
}

释放锁的姿势

// 如果释放锁成功
if (tryRelease(arg)) {
    // 让阻塞线程继续运行
}

1.2 实现不可重入锁

自定义同步器

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

/**
 * 自定义同步器
 * @author 晓风残月Lx
 * @date 2023/4/5 21:44
 */
public final class MySync extends AbstractQueuedSynchronizer {
    @Override
    protected boolean tryAcquire(int acquires) {
        if (acquires == 1) {
            if (compareAndSetState(0, 1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
        }
        return false;
    }

    @Override
    protected boolean tryRelease(int acquires) {
        if (acquires == 1) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        return false;
    }

    protected Condition newCondition() {
        return new ConditionObject();
    }

    @Override
    protected boolean isHeldExclusively() {
        return getState() == 1;
    }
}

自定义锁

有了自定义同步器,很容易复用 AQS ,实现一个功能完备的自定义锁

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

/**
 * 自定义锁
 * @author 晓风残月Lx
 * @date 2023/4/5 21:43
 */
public class MyLock implements Lock {

    static 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() {
        sync.release(1);
    }

    @Override
    // 生成条件变量
    public Condition newCondition() {
        return sync.newCondition();
    }
}

测试

import com.lv.juc.util.Sleeper;
import lombok.extern.slf4j.Slf4j;

/**
 * @author 晓风残月Lx
 * @date 2023/4/5 21:53
 */
@Slf4j
public class MyLockTest {

    public static void main(String[] args) {
        MyLock lock = new MyLock();

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking |...");
                //lock.lock();
				//log.debug("locking 2|...");
                Sleeper.sleep(1);
            } finally {
                log.debug("unlocking ...");
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking |...");
                Sleeper.sleep(1);
            } finally {
                log.debug("unlocking ...");
                lock.unlock();
                //lock.unlock();
            }
        }, "t2").start();
    }
}

在这里插入图片描述

测试不可重入的话,把注释都去掉

在这里插入图片描述

1.3 心得

起源

早期程序员会自己通过一种同步器去实现另一种相近的同步器,例如用可重入锁去实现信号量,或反之。这显然不 够优雅,于是在 JSR166(java 规范提案)中创建了 AQS,提供了这种通用的同步器机制。

目标

AQS 实现的功能目标

  • 阻塞版本获取锁 acquire 和 非阻塞的版本尝试获取锁 tryAcquire
  • 获取锁超时机制
  • 通过打断取消机制
  • 独占机制及共享机制
  • 条件不满足时的等待机制

设计

获取锁的逻辑

while(state 状态不允许获取) {
    if (队列中还没有此线程) {
    	入队并阻塞
    }
}
当前线程出队

释放锁的逻辑

if (state 状态允许了) {
	恢复阻塞的线程(s)
}

要点

  1. 原子维护 state 状态
  2. 阻塞及恢复线程
  3. 维护队列

1) state 设计

  • state 使用 volatile 配合 cas 保证其修改时的原子性
  • state 使用了 32 bit int 来维护同步状态,因为当时使用 long 在很多平台测试的结果并不理想

2)阻塞恢复设计

  • 早期的控制线程暂停和恢复的 api 有 suspend 和 resume,但它们是不可用的,因为如果先调用的 resume 那么 suspend 将感知不到
  • 解决方法是使用 park & unpark 来实现线程的暂停和恢复,先 unpark 再 park 也没问题
  • park & unpark 是针对线程的,而不是针对同步器,因此控制粒度更为精细
  • park 线程还可以通过 interrupt 打断

3)队列设计

  • 使用了 FIFO 先入先出队列,并不支持优先级队列
  • 设计时借鉴了 CLH 队列,它是一种单向无锁队列

在这里插入图片描述

队列中有 head 和 tail 两个指针节点,都用 volatile 修饰配合 cas 使用,每个节点有 state 维护节点状态

入队伪代码,只需要考虑 tail 赋值的原子性

do {
    // 原来的 tail
    Node prev = tail;
    // 用 cas 在原来 tail 的基础上改为 node
} while(tail.compareAndSet(prev, node))

出队伪代码

// prev 是上一个节点
while((Node prev=node.prev).state != 唤醒状态) {
}
// 设置头节点
head = node;

CLH 好处:

  • 无锁,使用自旋
  • 快速,无阻塞

AQS 在一些方面改进了 CLH

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // 队列中还没有元素 tail 为 null
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 将 node 的 prev 设置为原来的 tail
                node.prev = t;
                // 将 tail 从原来的 tail 设置为 node
                if (compareAndSetTail(t, node)) {
                    // 原来 tail 的 next 设置为 node
                    t.next = node;
                    return t;
                }
            }
        }
    }

AQS的并发工具类有

在这里插入图片描述

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

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

相关文章

剪枝与重参第六课:基于VGG的模型剪枝实战

目录基于VGG的模型剪枝实战前言1.Intro2.Prune实战2.1 说明2.2 test()2.3 加载稀疏训练模型2.4 前处理2.5 建立新模型并存储信息2.6 BatchNorm层的剪枝2.7 Conv2d的剪枝2.8 Linear的剪枝3.基于VGG的模型剪枝总结基于VGG的模型剪枝实战 前言 手写AI推出的全新模型剪枝与重参课程…

快排的递归实现

快速排序是一种时间复杂度低,但会虽随着数组的顺序变化,因为其效率之高被称为快速排序,而 且其不稳定性也可以同过优化进行解决。 快速排序的实现有三种方法: 1.hoare版 其基本思想为:任取待排序元素序列中 的某元…

3、如何使用GDB来进行命令行debug

文章目录一、与前面的联系二、GDB的一些认识1、什么是gdb2、gdb作用3、gdb可实现的功能三、GDB常用的调试命令一、与前面的联系 对于前面说到的launch.json文件就是用于debug的配置文件,在前面的vscode中我们可以发现配置好launch.json文件之后进行调试&#xff0c…

攻防世界-web2(逆向加密算法)

打开链接是PHP源码 给了一串密文,并对这串密文进行了一系列操作加密,注释里说解密$miwen就是flag 在此我们先介绍一些PHP内置函数: strrev(string): 反转字符串 strlen(string): 返回字符串的长度 substr(string, start, length): 返回字符…

认识、使用C++vetor和array

目录 前言: 1.vector模板 1.1vector简介 1.2创建vector类对象 2.array模板 2.1array简介 2.2创建array类对象 3.比较中学习 4.怎么避免数组越界访问 前言: 指针的基础用法分了近三篇文章,结合数组、结构、共用体、字符串一起学习。相…

【Golang | http】使用http库完成一个简单的POST请求

引言 主要记录使用Golang实现一个POST请求所用到的小知识点 1、项目结构 客户端向服务端注册用户信息,服务端返回注册信息中的用户名 PS E:\goland-workspace\GolangLearning\http> tree /f 卷 文件 的文件夹 PATH 列表 卷序列号为 0C66-1433 E:. ├─client…

小样本学习FSL介绍

1 概念 小样本学习(few-shot learning,FSL)旨在从有限的标记实例(通常只有几个)中学习,并对新的、未见过的实例进行识别。 相比于传统的深度学习和机器学习方法,小样本学习能够更好地模拟人类的…

从C出发 22 --- 变量的作用域与生命期

问题 1 : 这样子定义一个不属于任何函数的变量正确吗? 问题 2 : 编译能通过吗? 问题 : 我们要打印的var 到底是 10 还是 100. 总结: 什么都不会输出,因为这里的 i ;是让局部变量的 i ,程序会一直死循环 为什么都是 11,为什…

在构建个人想法时,使用哪个工具更好呢?Tana, AmpleNote 和 妙记多 Mojidoc的比较

笔记类 App 都很强调个人化,因为我们每个人会用不同的方法来做笔记、写日记。不过有一些框架可以帮助我们,比如子弹笔记(Bullet Journal)等。 Tana 和 Amplenote 都可以使用「标签」,尽管它们处理的方式、体验都大不相…

4.14~4.16学习总结

多线程: 同步代码块 格式:Synchronized(锁) { 操作共享数据的代码 } 特点1:锁默认打开,有一个线程进去了,锁自动关闭。 特点2:里面的代码全部执行完毕,线程处理,锁自动打开。 …

SaleSmartly(ss客服)怎么玩转Instagram自动化?

这段时间接触了不少粉丝,一直在说ins营销,说谁谁谁通过这个引流,结果爆了,那我们今天就来简单说一下。Instagram (IG) 是全球最大的照片和视频共享平台,拥有超过10亿的月活跃用户和 5 亿的日活跃Story用户。借助IG的强…

LNMP和论坛的搭建

系列文章目录 文章目录系列文章目录一、LNMP搭建1.承接上文搭建nginx服务2.Mysql数据库搭建3.安装配置 PHP 解析环境4.、部署 Discuz!社区论坛 Web 应用总结一、LNMP搭建 1.承接上文搭建nginx服务 2.Mysql数据库搭建 1、安装Mysql环境依赖包 yum -y install \ n…

不限量免注册,极速体验AI助手

最近 ChatGPT 很火,火到每个人都想玩一把,由于受限,不是在搭梯子就是在搭梯子的路上,现在类 ChatGPT 产品,它终于来了。还是先简单秀一波操作:第一波:大数据记录中,涉及关键字快速检…

vue2路由(上)

路由的简介 什么是路由? 用生活上的例子,路由器上的接口对应一个主机。 而由key和values组成的映射关系就是路由 主要用于SPA单页面应用 就是根据你端口号后面的路径,看你有没有配置这个页面对应的组件,如果有,那么就…

批处理脚本用法总结

目录一、常用命令二、基本语法1. rem 和 ::2. echo 和 3. pause4. errorlevel5. title6. color7. goto 和 :三、常见用法1. 设置临时环境变量2. 启动CMD执行命令3. 打开环境变量窗口参考资料:批处理(Batch),也称为批处理脚本。顾名思义,批处理…

零入门kubernetes网络实战-29->在同一个宿主机上基于虚拟网桥bridge链接不同网段的不同网络命名空间的通信方案

《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 上一篇文章,我们分享了使用虚拟网桥来连接同网段的不同网络命名空间下的通信情况。 那么,本篇文章, 我们想测试一下&…

用ChatGPT快速阅读论文:3个步骤让你轻松阅读论文文档

引言 您是否曾经因为阅读论文而感到困难和无从下手?ChatDOC是一款专为您设计的人工智能工具,帮助您快速理解论文内容。通过上传文档,利用ChatGPT技术,您只需3个简单步骤,即可快速阅读论文,提高阅读效率。立…

[架构之路-162]-《软考-系统分析师》-3-作系统基本原理-进程管理

目录 前言: 3 . 1 操作系统概述 3.1.1 操作系统的类型 2 . 批处理系统 3 . 分时操作系统 4 . 网络操作系统 5 . 分布式操作系统 6 . 嵌入式操作系统 3.1.2 操作系统的软件结构 1 . 整体结构 2 . 层次结构 3 . 客户/服务器结构 4 . 面向对象结构 3 . 2…

一文总结 Shiro 实战教程

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

InstructGLM:基于ChatGLM-6B在指令数据集上进行微调

InstructGLM 基于ChatGLM-6BLoRA在指令数据集上进行微调 https://github.com/yanqiangmiffy/InstructGLM 本项目主要内容: 🚀 2023/4/9 发布了基于100万条由BELLE项目生成的中文指令数据的Lora权重,具体可见output/belle/chatglm-lora.pt&a…