【juc第三章】:AQS机制全解

🔥你好我是fengxin_rou这是我的个人主页 fengxin_rou的主页

❄️欢迎查看我的专栏我的专栏

《Java后端学习》《JAVASE基础》《JUC并发》《redis》《JVM虚拟机》《MYSQL》《黑马点评》《rabbitmq》《JavaWeb+AI的talis学习系统》《苍穹外卖》

目录

前言:

[AQS 是什么?核心设计思想?](#AQS 是什么?核心设计思想?)

[AQS 底层结构:state 状态、双向阻塞队列?](#AQS 底层结构:state 状态、双向阻塞队列?)

[AQS 独占模式、共享模式区别?](#AQS 独占模式、共享模式区别?)

独占模式(Exclusive)

共享模式(Shared)

[AQS 排队、唤醒线程的流程?](#AQS 排队、唤醒线程的流程?)

[1、线程 排队(抢不到锁 → 进入等待)的流程](#1、线程 排队(抢不到锁 → 进入等待)的流程)

[二、线程唤醒(释放锁 → 叫醒别人) 的流程](#二、线程唤醒(释放锁 → 叫醒别人) 的流程)

AQS为什么要用双向链表

[1. 为了快速找到前驱节点(最核心原因)](#1. 为了快速找到前驱节点(最核心原因))

[2. 为了高效移除取消等待的线程节点](#2. 为了高效移除取消等待的线程节点)

[3. 为了保证队列不断裂](#3. 为了保证队列不断裂)

[4. 为了精准唤醒下一个线程](#4. 为了精准唤醒下一个线程)

总结


前言:

前面两篇学习了JUC并发的基础还有他的锁和关键字的知识,现在可以来了解一下JUC的底层框架AQS

AQS 是什么?核心设计思想?

AQS 全称 AbstractQueuedSynchronizer(抽象队列同步器) ,是 Java 并发包(JUC)的底层核心框架,也是 ReentrantLock、Semaphore、CountDownLatch 等工具的统一基石。

AQS核心思想是 ,如果被请求的共享资源空闲 ,那么就将当前请求资源的线程设置为有效 的工作线程,将共享资源设置为锁定状态;如果共享资源被占用 ,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列

它提供了一些基本的操作,如 acquire(获取资源)和 release(释放资源),这些操作会修改 state 的值,并根据 state 的值来判断线程是否可以获取或释放资源。AQS 的 acquire 操作通常会先尝试获取资源,如果失败,线程将被添加到等待队列中,并阻塞等待。release 操作会释放资源,并唤醒等待队列中的线程。

AQS 底层结构:state 状态、双向阻塞队列?

使用一个volatile state 变量表示锁状态:

state 是锁的属性,state=0 表示资源空闲,线程可以抢占; state≠0 表示资源被占用,线程无法直接获取。

底层维护一个 FIFO 双向链表队列:

抢不到锁的线程会被封装成节点,进入队列排队等待唤醒。

采用 CAS 无锁操作修改状态:

更新 state 时不需要使用 synchronized 加锁,而是通过CAS 原子指令实现无锁、安全、高效的更新。

AQS 独占模式、共享模式区别?

独占模式(Exclusive)

一句话:只有一个线程能拿到锁,其他人必须等。

  • 同一时刻只能有一个线程获取资源
  • state 被占用后,其他线程不能获取,state = 0才能拿资源
  • 线程获取:tryAcquire
  • 线程释放:tryRelease
  • 典型实现:ReentrantLock、读写锁的写锁
  • 用途:写操作、修改数据、保证线程安全

共享模式(Shared)

多个线程可以同时拿到资源,一起执行。

  • 同一时刻允许多个线程同时获取资源
  • state 代表可用数量,只要 >0 就能拿
  • 线程获取:tryAcquireShared
  • 线程释放:tryReleaseShared
  • 典型实现:Semaphore、CountDownLatch、读写锁的读锁
  • 用途:读操作、限流、多线程等待

AQS 排队、唤醒线程的流程?

1、线程 排队(抢不到锁 → 进入等待)的流程

  1. 线程来抢锁,先去CAS 修改 state
  2. 修改成功:拿到锁,直接执行业务代码
  3. 修改失败 :锁被别人占用,不能拿锁
  4. 把当前线程封装成一个 Node 节点
  5. 把节点 加入双向队列的尾部
  6. 检查前面的节点是否正常
  7. 确认安全后,调用 LockSupport.park()
  8. 线程进入 阻塞(挂起),不再运行,等待唤醒

二、线程唤醒(释放锁 → 叫醒别人) 的流程

  1. 拿到锁的线程执行完毕
  2. 调用释放方法,把 state 恢复为 0
  3. 去队列里找到 头节点的下一个有效线程
  4. 调用 LockSupport.unpark() 唤醒这个线程
  5. 被唤醒的线程退出阻塞
  6. 再次去 CAS 抢 state
  7. 抢成功 → 成为新的头节点,开始执行业务
  8. 执行完再释放,继续唤醒下一个

AQS为什么要用双向链表

1. 为了快速找到前驱节点(最核心原因)

线程在阻塞前,必须检查前面一个节点的状态,判断自己是否可以安全挂起。

  • 双向链表有 prev 指针,可以直接获取前驱节点
  • 单向链表没有向前的指针,无法快速找到前一个节点。

所以 必须用双向链表,才能实现安全阻塞逻辑


2. 为了高效移除取消等待的线程节点

如果线程等待超时、或被中断,需要从队列中删掉自己

  • 双向链表:直接通过 prevnext 把自己从队列摘除,O (1) 效率
  • 单向链表:必须从头遍历找到前驱,效率极低

高并发下大量线程取消等待,双向链表是唯一选择


3. 为了保证队列不断裂

高并发入队、出队时,单向链表很容易出现指针丢失,导致队列断裂 。 双向链表有前后双向指针互相兜底,即使某一个指针异常,队列依然完整。


4. 为了精准唤醒下一个线程

AQS 唤醒规则是:唤醒头节点的下一个节点

  • 双向链表通过 next 能立刻找到下一个等待线程。
  • 单向链表无法快速找到后继,唤醒效率低。

总结

  • 快速找到前驱节点,保证线程安全阻塞
  • 高效移除取消、中断、超时的节点
  • 高并发下防止队列断裂,提高稳定性;
  • 快速找到后继节点,实现精准唤醒
相关推荐
fengxin_rou2 天前
【JUC第二章下】:锁机制&关键字
架构·事务·cas·juc·volatile
fengxin_rou3 天前
【JUC第二章上】:锁机制&关键字
开发语言·并发·juc·
fengxin_rou4 天前
【juc面试第一章】:线程基础
线程·进程·juc
阿昌喜欢吃黄桃5 天前
如果线程池中线程异常后:销毁还是复用?
java·线程·线程池·多线程·juc
tongluowan0077 天前
Java中atomic底层原理 - ABA 问题与解决方案
java·juc·atomic
阿昌喜欢吃黄桃9 天前
并发线程工具类分享
java·线程池·多线程·并发·juc
长谷深风1119 天前
Java并发编程:线程安全与多线程实战指南【个人八股】
java·安全·线程·进程·juc·并发与并行·上下文切换(性能影响因素)
阿维的博客日记10 天前
怎么用ThreadLocal解决用户的登录上下文
java·juc
萧曵 丶13 天前
JUC 实际业务高频面试题浅谈
java·juc·aqs·lock