你真的弄明白了吗?Java并发之AQS详解

带着问题阅读

1、什么是AQS,它有什么作用,核心思想是什么

2、AQS中的独占锁和共享锁原理是什么,AQS提供的锁机制是公平锁还是非公平锁

3、AQS在Java中有哪些实现,如何基于AQS实现自己的锁控制

4、AQS除了提供锁框架以外还提供了什么能力

AQS介绍
AbstractQueuedSynchronizer(AQS) 提供了一套可用于实现锁同步机制的框架,不夸张地说, AQS 是 JUC 同步框架的基石。 AQS 通过一个 FIFO 队列维护线程同步状态,实现类只需要继承该类,并重写指定方法即可实现一套线程同步机制。

AQS 根据资源互斥级别提供了 独占和共享 两种资源访问模式;同时其定义 Condition 结构提供了 wait/signal 等待唤醒机制。在 JUC 中,诸如 ReentrantLock 、 CountDownLatch 等都基于 AQS 实现。

AQS框架
AQS原理

AQS 的原理并不复杂, AQS 维护了一个 volatile int state 变量和一个 CLH(三个人名缩写)双向队列 ,队列中的节点持有线程引用,每个节点均可通过 getState() 、 setState() 和 compareAndSetState() 对 state 进行修改和访问。·

当线程获取锁时,即试图对 state 变量做修改,如修改成功则获取锁;如修改失败则包装为节点挂载到队列中,等待持有锁的线程释放锁并唤醒队列中的节点。

AQS模版方法
AQS 内部封装了队列维护逻辑,采用模版方法的模式提供实现类以下方法:

tryAcquire(int);        // 尝试获取独占锁,可获取返回true,否则false
tryRelease(int);        // 尝试释放独占锁,可释放返回true,否则false
tryAcquireShared(int);  // 尝试以共享方式获取锁,失败返回负数,只能获取一次返回0,否则返回个数
tryReleaseShared(int);  // 尝试释放共享锁,可获取返回true,否则false
isHeldExclusively();    // 判断线程是否独占资源

如实现类只需实现独占锁/共享锁功能,可只实现 tryAcquire/tryRelease 或 tryAcquireShared/tryReleaseShared 。虽然实现 tryAcquire/tryRelease 可自行设定逻辑,但建议使用 state 方法对 state 变量进行操作以实现同步类。

如下是一个简单的同步锁实现示例:

public class Mutex extends AbstractQueuedSynchronizer {@Overridepublic boolean tryAcquire(int arg) {return compareAndSetState(0, 1);}@Overridepublic boolean tryRelease(int arg) {return compareAndSetState(1, 0);}public static void main(String[] args) {final Mutex mutex = new Mutex();new Thread(() -> {System.out.println("thread1 acquire mutex");mutex.acquire(1);// 获取资源后sleep保持try {TimeUnit.SECONDS.sleep(5);} catch(InterruptedException ignore) {}mutex.release(1);System.out.println("thread1 release mutex");}).start();new Thread(() -> {// 保证线程2在线程1启动后执行try {TimeUnit.SECONDS.sleep(1);} catch(InterruptedException ignore) {}// 等待线程1 sleep结束释放资源mutex.acquire(1);System.out.println("thread2 acquire mutex");mutex.release(1);}).start()}
}

示例代码简单通过 AQS 实现一个互斥操作,线程1获取 mutex 后,线程2的 acquire 陷入阻塞,直到线程1释放。其中 tryAcquire/acquire/tryRelease/release 的 arg 参数可按实现逻辑自定义传入值,无具体要求。

@param arg the acquire argument. This value is conveyed to {@link #tryAcquire} but is otherwise uninterpreted and can represent anyting you like.

AQS核心结构
Node
前文提到,在 AQS 中如果线程获取资源失败,会包装成一个节点挂载到 CLH 队列上, AQS 中定义了 Node 类用于包装线程。

Node 主要包含5个核心字段:

waitStatus :当前节点状态,该字段共有5种取值:
CANCELLED = 1 。节点引用线程由于等待超时或被打断时的状态。
SIGNAL = -1 。后继节点线程需要被唤醒时的当前节点状态。当队列中加入后继节点被挂起 (block) 时,其前驱节点会被设置为 SIGNAL 状态,表示该节点需要被唤醒。
CONDITION = -2 。当节点线程进入 condition 队列时的状态。(见 ConditionObject )
PROPAGATE = -3 。仅在释放共享锁 releaseShared 时对头节点使用。(见共享锁分析)
0 。节点初始化时的状态。
prev :前驱节点。
next :后继节点。
thread :引用线程,头节点不包含线程。
nextWaiter : condition 条件队列。(见 ConditionObject )
独占锁分析
acquire

public final void acquire(int arg) {// tryAcquire需实现类处理// 如获取资源成功,直接返回if (!tryAcquire(arg) && // 如获取资源失败,将线程包装为Node添加到队列中阻塞等待acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// 如阻塞线程被打断selfInterrupt();
}

acquire 核心为 tryAcquire 、 addWaiter 和 acquireQueued 三个函数,其中 tryAcquire 需具体类实现。 每当线程调用 acquire 时都首先会调用 tryAcquire ,失败后才会挂载到队列,因此 acquire 实现 默认为非公平锁 。

addWaiter 将线程包装为独占节点,尾插式加入到队列中,如队列为空,则会添加一个空的头节点。值得注意的是 addWaiter 中的 enq 方法,通过 CAS+自旋 的方式处理尾节点添加冲突。


acquireQueue 在线程节点加入队列后判断是否可再次尝试获取资源,如不能获取则将其前驱节点标志为 SIGNAL 状态(表示其需要被 unpark 唤醒)后,则通过 park 进入阻塞状态。


参照流程图, acquireQueued 方法核心逻辑为 for(;

你真的弄明白了吗?Java并发之AQS详解相关推荐

  1. Java并发之AQS详解(文章里包含了两片文章结合着看后边文章不清楚,请看原文)

          AQS全称抽象队列同步器(AbstractQuenedSynchronizer),它是一个可以用来实现线程同步的基础框架.当然,它不是我们理解的Spring这种框架,它是一个类,类名就是A ...

  2. Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  3. Java 并发之 AQS 详解(上)

    Java 并发之 AQS 详解 前言 Java SDK 为什么要设计 Lock 死锁问题 synchronized 的局限性 显式锁 Lock Lock 使用范式 Lock 是怎样起到锁的作用呢? 队 ...

  4. java aqs原理_Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  5. Java并发编程AQS详解

    本文内容及图片代码参考视频:https://www.bilibili.com/video/BV12K411G7Fg/?spm_id_from=333.788.recommend_more_video. ...

  6. Java技术之AQS详解

    AbstractQueuedSynchronizer 简写为AQS,抽象队列同步器.它是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS很容易并且高效的构造出来,以下都是通过AQS构造出来的: ...

  7. Java开发之ServLet详解

    一.什么是ServLet? serverLet是javaEE中运行于服务器端的,用于接收和响应HTTP协议的请求的程序. 二.ServLet的三种实现方式 1.实现ServLet接口 步骤: (1)实 ...

  8. Java并发编程之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  9. c语言sizeof char,sizeof 你真的弄明白了吗?来看看这个例子

    原标题:sizeof 你真的弄明白了吗?来看看这个例子 sizeof基础 在C语言中,sizeof是一个操作符(operator),而不是函数!其用于判断数据类型或者表达式长度(所占的内存字节数).其 ...

最新文章

  1. 如何使用JavaScript将数据附加到div?
  2. ue4 改变枢轴位置_UE4渲染模块概述(四)---反射
  3. ios配置pch文件及使用
  4. 《潜意识:控制你行为的秘密》摘录
  5. Java编程学习中必须掌握的13个核心技术
  6. 国庆活动延长三天!快来领取你的数据技术嘉年华门票!
  7. 程序设计基础-c#和java的区别
  8. windows c语言 http 状态检测_从软件架构说C语言
  9. 【通信】基于matlab GUI循环编码译码【含Matlab源码 1348期】
  10. 自定义 View 之抖音时钟罗盘仪效果
  11. python 绘图及可视化
  12. 深度学习计算模型中门函数的作用
  13. 基于Redis的限流器的实现
  14. JVM常用参数与工具
  15. TSC Deluxe 300 pro 打印机驱动
  16. 图片的缩放和拖拽功能
  17. collections库
  18. 获取淘宝/天猫、拼多多、京东商品详情API
  19. 前端接收bolb格式下载文件,前端下载blob格式的文件
  20. Oracle EBS R12开发工具安装

热门文章

  1. 2013工资新规定,未来的八种人将会被淘汰!
  2. ERP实施中要重视物料编码的规则
  3. ALV GRID学习笔记----Double Click事件
  4. SAP常见问题与解决办法 2
  5. 玖富(NASDAQ:JFU) :2019年Q4机构资金占比增至79.8%,科技赋能业务成果显著
  6. extract进程 oracle,ogg extract进程stoped问题
  7. scenebuilder各控件属性介绍_C#控件及常用设计整理(三)
  8. 计算机游戏系统分析,计算机游戏引擎fly3D系统的实现方式及应用技巧
  9. android o 小米note 3,小米 Note 3 MIUI 10 安卓 8.0 内测开启
  10. 微波炉定时c语言程序,微波炉控制系统c语言编程.doc