【并发编程】AQS原理

news/2024/5/20 7:53:20 标签: java, 开发语言, 并发编程, JUC

       📝个人主页:五敷有你      

 🔥系列专栏:并发编程

⛺️稳中求进,晒太阳

1. 概述

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

特点:

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

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

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

获取锁的姿势

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

释放锁的姿势

java">// 如果释放锁成功
if (tryRelease(arg)) { 
// 让阻塞线程恢复运行
}

2. 实现不可重入锁

自定义同步器

java">//同步器类
class MySync extends AbstractQueuedSynchronizer {
    @Override
    protected boolean tryAcquire(int arg) {
        if (compareAndSetState(0,1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    @Override
    protected boolean tryRelease(int arg) {
        setExclusiveOwnerThread(null);
       setState(0);

       return true;
    }

    @Override //是否持有独占锁
    protected boolean isHeldExclusively() {
        if(getExclusiveOwnerThread()!=null){
            return true;
        }
        return false;
    }
    public Condition newCondition(){
        return  new ConditionObject();
    }

}

自定义锁

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

java">public class MyLock implements Lock {
    MySync mySync=new MySync();

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

}

@Override //加锁,可以被打断
public void lockInterruptibly() throws InterruptedException {
    mySync.acquireInterruptibly(1);
}

@Override //尝试加锁(一次)
public boolean tryLock() {
    mySync.tryAcquire(1);
    return true;
}

@Override //尝试加锁(带超时时间)
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
   mySync.tryAcquireNanos(1,unit.toNanos(time));
   return true;
}

@Override //解锁
public void unlock() {
    mySync.release(1);
}

@Override  //创建条件变量
public Condition newCondition() {
    return mySync.newCondition();
}

}

测试一下

java">public static void main(String[] args) {
    Lock myLock=new MyLock();
    new Thread(()->{
        myLock.lock();
        try {
            System.out.println("locking...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            System.out.println("unlock...");
            myLock.unlock();
        }


    },"t1").start();
    new Thread(()->{
        myLock.lock();
        try {
            System.out.println("加锁...");

        }finally {
            System.out.println("解锁...");
            myLock.unlock();
        }
    },"t2").start();

}

心得

起源

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

目标

AQS 要实现的功能目标

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

设计

AQS 的基本思想其实很简单

获取锁的逻辑

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

释放锁的逻辑

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

要点

  • 原子维护 state 状态
  • 阻塞及恢复线程
  • 维护队列
1) state 设计
  • state 使用 volatile 配合 cas 保证其修改时的原子性
  • state 使用了 32bit int 来维护同步状态,因为当时使用 long 在很多平台下测试的结果并不理想
2) 阻塞恢复设计
  • 早期的控制线程暂停和恢复的 api 有 suspend 和 resume,但它们是不可用的,因为如果先调用的 resume那么 suspend 将感知不到
  • 解决方法是使用 park & unpark 来实现线程的暂停和恢复,具体原理在之前讲过了,先 unpark 再 park 也没问题
  • park & unpark 是针对线程的,而不是针对同步器的,因此控制粒度更为精细
  • park 线程还可以通过 interrupt 打断
3) 队列设计
  • 使用了 FIFO 先入先出队列,并不支持优先级队列
  • 设计时借鉴了 CLH 队列,它是一种单向无锁队列

主要用到AQS的并发工具类


http://www.niftyadmin.cn/n/5378226.html

相关文章

一览大模型长文本能力

前言 如今的大模型被应用在各个场景,其中有些场景则需要模型能够支持处理较长文本的能力(比如8k甚至更长),其中已经有很多开源或者闭源模型具备该能力比如GPT4、Baichuan2-192K等等。 那关于LLM的长文本能力,目前业界通常都是怎么做的&…

Unity类银河恶魔城学习记录7-5 p71 Improving sword throwing state源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Sword_Skill.cs using System.Collections; using System.Collections.Ge…

算法导论记录丨16.1 活动选择、877. 石子游戏

16.1 活动选择 最优子结构 活动选择问题的最优子结构意味着问题的最优解包含了其子问题的最优解。具体来说,如果我们有一个按结束时间排序的活动集合 S{a1​,a2​,...,an​},并且 S’ 是 S 的最大兼容活动子集,那么对于 S′ 中的任何活动aj…

vue-组件组成和组件通信(四)

组件的三大组成部分 (结构/样式/逻辑) scoped样式冲突 默认情况:写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。 1. 全局样式: 默认组件中的样式会作用到全局 2. 局部样式: 可以给组件加上 scoped 属性, 可以让样式只作用于当前组…

Pytest测试技巧之Fixture:模块化管理测试数据

在 Pytest 测试中,有效管理测试数据是提高测试质量和可维护性的关键。本文将深入探讨 Pytest 中的 Fixture,特别是如何利用 Fixture 实现测试数据的模块化管理,以提高测试用例的清晰度和可复用性。 什么是Fixture? 在 Pytest 中&a…

(02)Hive SQL编译成MapReduce任务的过程

目录 一、架构及组件介绍 1.1 Hive底层架构 1.2 Hive组件 1.3 Hive与Hadoop交互过程 二、Hive SQL 编译成MR任务的流程 2.1 HQL转换为MR源码整体流程介绍 2.2 程序入口—CliDriver 2.3 HQL编译成MR任务的详细过程—Driver 2.3.1 将HQL语句转换成AST抽象语法树 词法、语…

Vue路由的传参

Vue传参方式可以划分为params传参(参数隐藏在路径中)和query传参(参数在?后)俩种方式 1. 使用 router-link 标签跳转路由 要注意 to 和 :to 的不同: to 不带参数 , :to 带参数 &#xff0…

Fabric自动化部署

Fabric自动化部署是一种基于Python的自动化部署工具,它可以帮助开发人员自动化地执行一系列部署任务,如代码推送、服务器配置更新、文件传输等。 Fabric自动化部署的核心是一个名为fabfile.py的Python文件,其中定义了要执行的部署任务。通过…