• JAVA并发之谈谈你对AQS的理解


    一、AQS是什么

    AQS的全称是 (AbstractQueuedSynchronizer ),它定义了一套多线程访问共享资源的同步器框架,是 J.U.C 包中多个组件的底层实现,如 Lock、CountDownLatch、Semaphore 等都用到了 AQS。

    通过查看AbstractQueuedSynchronizer的实现类如下图:
    aqs

    二、AQS具备哪些特性

    1. 阻塞等待队列
      1.1 获取锁失败的线程会进入到一个阻塞等待队列中。
    2. 共享 / 独占
      从本质上来说,AQS 提供了两种锁机制,分别是独占锁,和共享锁。
      2.1 独占锁,就是存在多线程竞争同一共享资源时,同一时刻只允许一个线程访问该共享资源,也就是多个线程中只能有一个线程获得锁资源,比如 Lock 中的ReentrantLock 重入锁实现就是用到了 AQS 中的排它锁功能。
      2.2 共享锁也称为读锁,就是在同一时刻允许多个线程同时获得锁资源,比如CountDownLatch 和 Semaphore 都是用到了 AQS 中的共享锁功能。
    3. 公平/非公平
      3.1 公平:线程在获取锁失败时,直接进入阻塞队列。
      3.2 非公平:线程在获取锁失败时,进入阻塞队列总会再尝试一次获取锁,插队
    4. 可重入
      4.1 对于同一把锁,获取锁的当前线程可以重复获取。
    5. 允许中断
      5.1 提供中断机制,来干预线程之间的通信或协作。

    三、用的哪种设计模式

         模板方法模式

    四、AQS与锁二者之间的关系

    1. 锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节。
    2. 同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。
    3. 锁和同步器很好地隔离了使用者和实现者所需关注的领域。

    看两张图吧:

    以独占锁ReentrantLock为例:

    ReentrantLock

    ReentrantLock实现了Lock的接口,没有直接与AQS交互,而是通过一个内部Sync类继承AQS,将同步器的所有调用都映射到对应的Sync对应的方法。

    ReentrantLock加锁方法 lock():

    lock

    查看 Sync的类图继承关系如下:

    在这里插入图片描述

    五、如何基于AQS实现一把独占锁

    1. 因为AQS内部帮我们把像多线程访问共享资源获取锁失败时,线程 入队、出队、阻塞、唤醒 提供好了,所以实现一把锁非常容易。
    2. 我们只需要关注加锁方法 tryAcquire 和释放锁资源方法 tryRelease 即可,其他的AQS已经为我们实现好了。

    代码如下:

    /**
     * Created with IntelliJ IDEA.
     * @Author: zhaoxn
     * @Date: 2022/11/25/21:51
     * @Description:基于AQS实现一把互斥锁
     */
    public class MutexLock extends AbstractQueuedSynchronizer{
    
        @Override
        protected boolean tryAcquire(int unused) {
            //cas 加锁
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
    
        @Override
        protected boolean tryRelease(int unused) {
            //释放锁
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
    
        //调用AQS内部的tryAcquire方法获取锁,失败需要入队或成功直接返回
        public void lock() {
            acquire(1);
        }
    
        //调用自己重写的tryAcquire方法获取锁,失败或成功直接返回
        public boolean tryLock() {
            return tryAcquire(1);
        }
    
        //重写释放锁资源的方法,至于释放锁以后唤醒队列中阻塞的线程,交给AQS内部就好了
        public void unlock() {
            release(1);
        }
        
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    六、参考资料

             Java并发编程的艺术

    🎯更多关于AQS文章可查看:🎯

  • 相关阅读:
    【cocos源码学习】模板示例工程的目录说明
    【MySQL系列】MySQL数据库基础
    Mybatis实战练习二【查询详情】
    leetcodeTop100(32) 合并链表数组
    目前和未来的缓存构建
    ESP8266-Arduino网络编程实例-扫描WiFi可用网络
    C语言,字符串压缩。一个非常 好的学习debug的案例。
    Linux基础命令
    【系统设计系列】异步和网络通信
    软件测试 - 测试基础知识梳理
  • 原文地址:https://blog.csdn.net/qq_39939541/article/details/128043183