• 一文让你彻底搞懂AQS(通俗易懂的AQS)


    一文让你彻底搞懂AQS(通俗易懂的AQS)

    一、什么是AQS

    AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。

    二、前置知识

    学习AQS需要大家对同步锁有一定的概念。同时大家要知道LockSupport的使用,可以参考我这篇文章。(LockSupport从入门到深入理解

    三、AQS 的核心思想

    AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。 CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 (图一为节点关系图)

    private volatile int state;//共享变量,使用volatile修饰保证线程可见性
    
    • 1

    在这里插入图片描述

    四、AQS 案例分析

    上面讲述的原理还是太抽象了,那我我们上示例,结合案例来分析AQS 同步器的原理。以ReentrantLock使用方式为例。
    代码如下:
    
    • 1
    • 2
    public class AQSDemo {
        private static int num;
    
    
        public static void main(String[] args) {
    
            ReentrantLock lock = new ReentrantLock();
    
    
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock.lock();
                    try {
                            Thread.sleep(1000);
                            num += 1000;
                        System.out.println("A 线程执行了1秒,num = "+ num);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    finally {
                        lock.unlock();
                    }
                }
            },"A").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock.lock();
                    try {
                        Thread.sleep(500);
                        num += 500;
                        System.out.println("B 线程执行了0.5秒,num = "+ num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    finally {
                        lock.unlock();
                    }
                }
            },"B").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock.lock();
                    try {
                        Thread.sleep(100);
                        num += 100;
                        System.out.println("C 线程执行了0.1秒,num = "+ num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    finally {
                        lock.unlock();
                    }
                }
            },"C").start();
        }
    }
    
    
    
    • 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
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    执行的某一种结果! 这个代码超级简单,但是执行结果却是可能不一样,大家可以自行实验。
    结果一
    在这里插入图片描述
    在这里插入图片描述
    对比一下三种结果,大家会发现,无论什么样的结果,num最终的值总是1600,这说明我们加锁是成功的。

    五、AQS 源码分析

    使用方法很简单,线程操纵资源类就行。主要方法有两个lock() 和unlock90.我们深入代码去理解。我在源码的基础上加注释,希望大家也跟着调试源码。其实非常简单。

    5.1 lock源码分析

    5.2 unlock源码分析

  • 相关阅读:
    SpringBoot 条件注解之:自定义条件注解
    【毕业设计】基于大数据的京东消费行为分析与可视化 - python 机器学习
    vue基于vant实现图片上传
    Redis下载安装配置(linux版本)
    【C++】教大家在七夕new一个对象
    护眼灯买哪种好? 推荐五款儿童护眼台灯
    某网站获取到正文内容
    Vue(七)——Vue中的Ajax
    数据仓库基础
    组件命名报错 “Component name “XXX“ should always be multi-word”的解决方法
  • 原文地址:https://blog.csdn.net/u010445301/article/details/125590758