AQS(AbstractQueuedSynchronizer)是什么?

news2024/9/21 16:50:18

目录

  • 简介
  • 原理
    • 概览
    • 资源的共享方式
      • 独占(Exclusive)
      • 共享(Shared)
    • 模板方法模式在AQS中的应用
  • 经典应用
    • ReentrantLock
    • Semaphore

简介

AQS全称AbstractQueuedSynchronizer,位于java.util.concurrent.locks包下,它是一个用来构建锁和同步器的框架,使用AQS能简单且高效的构造出应用广泛的大量同步器,像ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch等等都是基于AQS的,我们自己也能利用AQS非常轻松的构造出符合自己需求的同步器。

原理

概览

AQS的核心思想是:如果被请求的资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且修改资源的状态;如果被请求的资源被占用,那么就需要一套线程阻塞等待和被唤醒时锁分配的机制,这个机制AQS使用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagerste)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成CLH队列的一个结点(Node)来实现锁的分配。

在这里插入图片描述

AQS使用一个int类型成员变量来表示状态:

private volatile int state;

状态通过如下三个方法进行操作:

protected final int getState() {
    return state;
}

protected final void setState(int newState) {
    state = newState;
}

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

其中compareAndSetState以CAS的方式实现对状态的原子更新。

资源的共享方式

独占(Exclusive)

当一个线程在独占模式下获取到了资源,其他线程都无法获取该资源,像ReentrantLock就是独占模式。

共享(Shared)

在共享模式下,支持多线程获取资源,但并不一定每个线程都能成功获取,这取决于子类怎么实现(AbstractQueuedSynchronizer是一个抽象类,有些方法需要子类去实现)。像Semaphore、CountDownLatch都是共享模式,而ReentrantReadWriteLock既支持独占模式(写锁),也支持共享模式(读锁)。

不同的自定义同步器争用资源的方式不同,自定义同步器在实现时只需要实现资源的获取与释放方式即可(其实就是state值的维护,比如获取资源state减一,释放资源state加一,state小于等于0时无法获取资源等),至于线程等待队列的维护(如获取资源失败入队、唤醒出队等),AQS已经帮我们实现好了。

模板方法模式在AQS中的应用

AQS的设计是基于模板方法模式的,如果需要自定义同步器,一般的方式是这样:

  1. 继承AbstractQueuedSynchronizer并重写指定的方法(这些重写方法很简单,无非是对state的值进行操作)。
  2. 将AQS组合在自定义同步组件的实现中,并调用其方法,而这些方法会调用使用者重写的方法。

自定义同步器时需要重写如下几个AQS提供的模板方法:

// 尝试以独占的方式获取资源,这个方法需要判断state的值是否允许获取资源,如果允许,那就获取。
// 在调用acquire方法时会调用这个方法,如果返回false,线程可能会加入CLH队列并阻塞,直到其他线程调用release方法唤醒它
// 返回true代表成功,false代表失败
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 尝试以独占的方式释放资源
// 返回true代表成功,false代表失败
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// 尝试以共享的方式获取资源,这个方法需要判断state的值是否允许获取资源,如果允许,那就获取。
// 在调用acquireShared方法时会调用这个方法,如果返回值小于0,线程可能会加入CLH队列并阻塞,
// 直到其他线程调用releaseShared方法唤醒它
// 返回负数代表失败
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

// 尝试以共享的方式释放资源
// 返回true代表成功,false代表失败
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

// 该线程是否正在独占资源。只有用到condition才需要去实现它。
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中其他方法都被final修饰,所以无法重写,只有这几个方法可以自定义。

我们在使用时,直接调用的是这几个方法:

// 以独占的方式获取资源
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 以独占的方式释放资源
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 以共享的方式获取资源
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

// 以共享的方式释放资源
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

可以看到,这些方法里会调用我们自己实现的方法。

经典应用

ReentrantLock

ReentrantLock又叫可重入锁,它是基于AQS的,实现的是独占模式。它支持公平锁和非公平锁,所以它有两套同步器来支持这两种锁机制,分别是NonfairSync和FairSync,ReentrantLock把这两个同步器的公共代码抽象到内部类Sync中,我们来看下Sync的代码:

// 此时AQS的state代表持有锁的次数(注意,不是持有锁的线程数,同一个线程多次获取锁state也会增加)
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    // ReentrantLock的lock最终调用的就是这个方法
    abstract void lock();

    // 非公平版本的tryAcquire最终就是调用这个方法,AQS的tryAcquire方法放在Sync的子类中重写
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) { // 如果锁没有被持有
            // 使用CAS的方式设置state的值
            // state预期为0,设置为acquires(这里是1)
            if (compareAndSetState(0, acquires)) { 
                setExclusiveOwnerThread(current); // 将当前线程设置为占用资源的线程
                return true;
            }
        }
        // 当前线程为占用资源的线程,因为是可重入锁,所以这里可以继续修改state
        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;
    }

    // 重写AQS的tryRelease方法
    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;
    }

    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Methods relayed from outer class

    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }

    final boolean isLocked() {
        return getState() != 0;
    }

    /**
     * Reconstitutes the instance from a stream (that is, deserializes it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

再看下NonfairSync的代码:

// 非公平锁同步器
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    // 实现Sync的lock方法
    final void lock() {
        // 直接用CAS的方式抢锁,无需排队,非公平性就体现在这里
        // state预期为0(资源没有被占用),将state设置为1
        if (compareAndSetState(0, 1)) 
            setExclusiveOwnerThread(Thread.currentThread()); // state修改成功,设置当前线程为占用资源的线程
        else // 抢锁失败调用AQS的acquire方法,如果还是抢锁失败,会进入队列排队
            acquire(1);
    }

    // 重写AQS的tryAcquire方法
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires); // 调用父类Sync的nonfairTryAcquire方法
    }
}

我们发现,非公平锁的非公平性体现在调用lock方法时会直接使用CAS修改state的值而无需排队,只有在CAS失败之后才会调用AQS的acquire方法,acquire方法包含了入队等待和唤醒那一套机制。

接下来看下FairSync的代码:

// 公平锁同步器
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    // 实现Sync的lock方法 
    final void lock() {
        acquire(1); // 直接调用AQS的acquire方法
    }

    // 重写AQS的tryAcquire方法
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) { // 锁没有被持有
            if (!hasQueuedPredecessors() && // 在队列里没有排在当前线程前面的线程,公平性就体现在这里
                compareAndSetState(0, acquires)) { // 修改state值成功
                setExclusiveOwnerThread(current); // 设置当前线程为占用资源的线程
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

从代码可以看出,公平锁同步器的lock方法直接调用AQS的acquire方法,而acquire方法包含了入队等待和唤醒那一套机制。

ReentrantLock在构造器中指定使用哪个同步器,默认使用非公平同步器,因为它的吞吐量更大:

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock的lock方法实际上调用的是同步器的lock方法:

public void lock() {
    sync.lock();
}

而unlock实际上调用的是同步器的release方法:

public void unlock() {
    sync.release(1);
}

Semaphore

Semaphore中文一般叫信号量,它可以控制同时访问共享资源的线程数,所以它最常见的使用场景是用来控制并发(限流)。Semaphore也是基于AQS的,它实现的是共享模式。跟ReentrantLock一样,它也支持公平和非公平两种锁机制,分别对应FairSync和NonfairSync这两个同步器。

NonfairSync和FairSync的公共代码被提取到内部类Sync中,首先我们看下Sync的代码:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;

    // permits是许可数,每当有一个线程访问共享资源,permits就减一
    // 当permits小于等于0时,线程访问共享资源会被阻塞
    Sync(int permits) {
        setState(permits); // permits即是AQS中的state
    }

    final int getPermits() {
        return getState();
    }

    // 非公平的方式获取共享资源
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                // 直接使用CAS修改state值,不用管前面有没有线程排队,非公平性就体现在这里
                compareAndSetState(available, remaining)) 
                return remaining; // 返回负数代表异常
        }
    }

    // 重写AQS的tryReleaseShared方法
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            int next = current + releases;
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            if (compareAndSetState(current, next))
                return true;
        }
    }

    final void reducePermits(int reductions) {
        for (;;) {
            int current = getState();
            int next = current - reductions;
            if (next > current) // underflow
                throw new Error("Permit count underflow");
            if (compareAndSetState(current, next))
                return;
        }
    }

    final int drainPermits() {
        for (;;) {
            int current = getState();
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

再看下NonfairSync的代码:

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

    NonfairSync(int permits) {
        super(permits);
    }

    // 重写AQS的tryAcquireShared
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires); // 调用父类Sync的nonfairTryAcquireShared方法
    }
}

最后看下FairSync的代码:

static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;

    FairSync(int permits) {
        super(permits);
    }

    protected int tryAcquireShared(int acquires) {
        for (;;) {
            if (hasQueuedPredecessors()) // 在队列里有排在当前线程前面的线程
                return -1; // 返回失败,因为公平锁需要排队
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

Semaphore可以在构造器中指定许可数和公平性:

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

它对外暴露的主要方法中,直接或间接调用的都是我们在同步器中定义的方法。

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

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

相关文章

Kubernetes (k8s)在企业项目中的重点应用场景以及云原生和云架构的原理

Kubernetes &#xff08;k8s&#xff09;在企业项目中的重点应用场景以及云原生和云架构的原理。 Kubernetes&#xff0c;简称 K8s&#xff0c;是用 8 代替中间 8 个字符 “ubernete” 而成的缩写&#xff0c;是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应…

Allegro因为DRC报错无法使用走线居中命令的解决办法

Allegro因为DRC报错无法使用走线居中命令的解决办法 在用Allegro做PCB设计的时候,走线居中是非常实用的功能 但是这个功能只能在走线居中不会产生DRC的使用。 如果居中后仍然存在DRC,比如间距,等长等等DRC,如下图: 使用居中命令就会出现报错,如图,因为居中后线距离孔的…

图文详解Linux中的火墙策略优化

目录 前言 一、火墙管理工具切换 二、iptables 的使用 三、火墙默认策略 四、firewalld的使用 1、firewalld的开启 2、关于firewalld的域 3、关于firewalld的设定原理及数据存储 4、firewalld的管理命令 5、firewalld的高级规则 6、firewalld中的NAT 总结 前言 火…

【软件测试】性能测试面试题分析与回答,你的优势不止这些......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 软件测试这一岗已悄…

Java原型模式以及引用拷贝与对象拷贝问题

目录基本数据类型&#xff0c;引用数据类型&#xff0c;String引用拷贝对象拷贝浅拷贝深拷贝原型模式基本数据类型&#xff0c;引用数据类型&#xff0c;String 这里为了更好的理解栈&#xff0c;堆的指向关系&#xff0c;Java传值&#xff0c;传引用问题&#xff0c;我找来一…

全网最详细地介绍mybatis-plus框架

文章目录1. 简介2. 特性3. 支持数据库4. 框架结构5. 开始使用5.1 数据源5.2 初始化工程6. 总结之前使用mybatis框架时&#xff0c;需要写大量的xml配置文件&#xff0c;维护起来比较繁琐。现在使用mybatis-plus&#xff0c;若是简单的curd操作&#xff0c;可以不用写xml文件&am…

maxwell解析mysql的binlog数据并保存到kafka使用

通过maxwell来实现binlog的实时解析&#xff0c;实现数据的实时同步 1、mysql创建一个maxwell用户 为mysql添加一个普通用户maxwell&#xff0c;因为maxwell这个软件默认用户使用的是maxwell这个用户&#xff0c; 进入mysql客户端&#xff0c;然后执行以下命令&#xff0c;进…

IDEA操作git commit后(push项目失败:Access token is expired),撤销commit,恢复到提交前的状态

1. 在IDEA操作push代码报错 remote: [session-e6423190] Oauth: Access token is expired 原因&#xff1a;这个问题其实就是因为你的本地电脑上安全中心存储Gitee密码过期导致的。 解决此问题可以参考以下链接&#xff1a;本以为修改下IDEA的settings下的Gitee账号密码就可以了…

若依框架文档开发手册----开发中常用功能模块

目录 前端 add.html 时间框 大文本框 Ajax校验 自定义校验 回显选中图片 JS对添加下拉列元素 edit.html 下拉列 回显时间 list.html 搜索栏 时间框 mapper.xml Table表格 格式化时间 前端 表格匹配字典值 表格增加.减少功能项 全局 其他 关闭标签页 输入框…

前端使用vue-pdf、pdf-lib、canvas 给PDF文件添加水印,并预览与下载

前端使用vue-pdf、pdf-lib 给pdf添加水印&#xff0c;并预览与下载效果预览使用第三方插件安装依赖插件import 导入依赖预览添加水印的pdf下载添加水印的pdf预览及下载总结完整代码效果预览 使用第三方插件 安装依赖插件 npm i vue-pdf --save npm i pdf-lib --save npm inst…

java之面向对象基础

1.类和对象1.1什么是对象万物皆对象&#xff0c;只要是客观存在的事物都是对象1.2什么是面向对象1.3什么是类类是对现实生活中一类具有共同属性和行为的事物的抽象类的特点&#xff1a;类是对象的数据类型类是具有相同属性和行为的一组对象的集合1.4什么是对象的属性属性&#…

微信小程序——使用npm包,安装 Vant weapp 组件库安装教程及使用vant组件

一.小程序对 npm 的支持与限制目前&#xff0c;小程序中已经支持使用 npm 安装第三方包&#xff0c;从而来提高小程序的开发效率。但是&#xff0c;在小程序中使用 npm 包有如下3个限制&#xff1a;&#x1f4dc;不支持依赖于 Node . js 内置库的包&#x1f4dc;不支持依赖于浏…

【软件测试】2023年的软件测试咋样?见鬼,我到底该如何进阶?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 一谈到进阶&#xf…

Sitecore本地安装详细介绍

一、简介 Sitecore 是一种 CMS(内容管理系统,位于 Web 前端和后端办公系统或流程之间的软件系统),本文已当前最新的 10.2.0 版本为例,介绍如何安装部署。 二、环境准备 Sitecore 依赖于 IIS、SQL Server,在后续 Sitecore 安装之前,这两依赖需要提前安装完成 2.1 II…

【CTF】ctf中用到的php伪协议总结及例题(持续更)

目录 前言 关于文件包含漏洞 php伪协议总结 关于php://协议 参考自&#xff1a; 前言 本篇文章使用的靶场是buuctf上的web题目&#xff1a;[BSidesCF 2020]Had a bad day 进行点击选项得到一个这样的url 这里猜测存在sql注入&#xff0c;没测出来。或者可能有php伪协议读…

excel函数应用:如何写出IF函数多级嵌套公式

说到函数就不得不提起函数中最受欢迎的三大家族&#xff1a;求和家族、查找引用家族、逻辑家族&#xff01;&#xff01;&#xff01;没错&#xff01;今天我们要介绍的就是三大家族之一逻辑函数家族的领头人&#xff1a;IF函数——很多人难以理解IF函数的多级嵌套使用。其实&a…

shell 函数详解

目录 函数 一&#xff0c;什么是函数 二&#xff0c; 函数的返回值 三&#xff0c;函数语法 示例1&#xff1a; 示例2&#xff1a; 四&#xff0c;函数的调用 示例1&#xff1a; 示例2&#xff1a; 五&#xff0c;函数库文件 六&#xff0c; 递归函数 示例1&#xf…

Node.js 全局对象介绍

在学习 Javascript 之初&#xff0c;会接触一个概念&#xff1a;JS 由三部分组成&#xff0c;DOM BOM ECMAScript。其中前两者是宿主环境&#xff0c;也就是浏览器所提供的能力。后者才是 JS 语言本身的标准。 在上篇文章《Node.js入门&#xff08;1&#xff09;&#xff1a…

SpringMVC之响应

目录 一&#xff1a;环境准备 二&#xff1a;响应页面[了解] 三&#xff1a;返回文本数据[了解] 四&#xff1a;响应JSON数据 SpringMVC接收到请求和数据后&#xff0c;进行一些了的处理&#xff0c;当然这个处理可以是转发给Service&#xff0c;Service层再调用Dao层完成的…

1月更新!EasyOps® 28+新功能“狂飙”上线~

2023节后&#xff0c;我们就要“搞事情”&#xff01; 一波新功能已上线&#xff0c;快看是不是你需要的&#xff01; 持续升级优化全平台产品&#xff0c; 只为成为你数字化变革最值得信赖的合作伙伴&#xff01; 优维EasyOps全平台28新功能来了&#xff01; ↓↓↓ 1、H…