网站首页 > 技术教程 正文
AbstractQueuedSynchronizer(AQS)是Java并发编程中一个核心的类,它提供了实现锁、同步器等基础工具的框架,比如ReentrantLock、CountDownLatch、Semaphore等都是基于AQS实现的。在本文中,我们将深入AQS的源码,探究它的实现原理以及其中的关键数据结构和算法。
AQS的设计思路
在介绍AQS的实现原理之前,我们先来看看它的设计思路。AQS的设计思路基于以下两个核心概念:
状态
AQS维护了一个volatile类型的state变量,用于表示同步状态。在不同的同步器中,state的含义不同,比如在ReentrantLock中,它表示锁的持有次数,而在CountDownLatch中,它表示需要等待的线程数。通过修改state变量的值,AQS实现了不同的同步功能。
等待队列
AQS维护了一个FIFO的等待队列,用于保存等待获取同步状态的线程。在AQS中,线程获取同步状态失败时,会被加入到等待队列中,直到获得同步状态或者被中断或者超时才会从等待队列中移除。
通过状态和等待队列,AQS实现了同步器的基础框架。不同的同步器只需要继承AQS并实现特定的方法即可。
AQS的核心数据结构
在AQS中,有几个关键的数据结构,它们是实现AQS同步功能的基础。
Node
Node是AQS中最重要的数据结构之一,它表示等待队列中的一个节点。每个Node节点都有一个waitStatus变量,用于表示节点的状态,比如CANCELLED、SIGNAL、CONDITION等。waitStatus变量的含义在不同的同步器中有不同的解释。
在等待队列中,Node节点通过prev和next两个指针链接在一起,形成一个FIFO的双向链表。通过prev指针,可以找到当前节点的前驱节点,通过next指针,可以找到当前节点的后继节点。
Head和Tail
等待队列中的第一个节点称为Head节点,最后一个节点称为Tail节点。Head和Tail节点并不是通过prev和next指针链接在等待队列中的,而是通过两个volatile类型的变量head和tail记录的。这是为了在高并发场景下,能够快速地对等待队列进行修改。
State
State是AQS中另一个非常重要的数据结构,它是一个volatile类型的变量,用于表示同步状态。State的含义在不同的同步器中有不同的解释。
AQS的核心算法
在AQS中,等待队列的管理是通过一组方法来实现的。其中最重要的方法是acquire()和release()方法。
acquire()方法
acquire()方法用于获取锁,它是AQS中最核心的方法之一。acquire()方法有两个版本,一个是独占式获取锁的版本,一个是共享式获取锁的版本。
独占式获取锁
独占式获取锁是通过acquire()方法的独占式版本来实现的。这个方法有一个参数,表示获取锁的状态值。当一个线程请求锁时,如果锁已经被占用,那么该线程将会被加入到等待队列中,直到锁被释放。
在独占式获取锁的实现中,AQS会首先通过CAS方法来修改状态值。如果修改成功,那么表示当前线程成功获取到了锁。如果修改失败,那么表示当前锁已经被其他线程持有,需要将当前线程加入到等待队列中。
当一个线程成功获取到锁之后,它需要在释放锁之前一直持有锁。如果有其他线程在等待队列中等待锁,那么它们会被唤醒,直到其中一个线程成功获取到锁为止。
共享式获取锁
共享式获取锁是通过acquireShared()方法的共享式版本来实现的。这个方法有一个参数,表示获取锁的状态值。共享式获取锁可以被多个线程同时持有。
在共享式获取锁的实现中,AQS会首先通过CAS方法来修改状态值。如果修改成功,那么表示当前线程成功获取到了锁。如果修改失败,那么表示当前锁已经被其他线程持有,需要将当前线程加入到等待队列中。
当一个线程成功获取到锁之后,它需要在释放锁之前一直持有锁。如果有其他线程在等待队列中等待锁,那么它们会被唤醒,直到所有的等待线程都成功获取到锁为止。
release()方法
release()方法用于释放锁,它是AQS中最核心的方法之一。release()方法有两个版本,一个是独占式释放锁的版本,一个是共享式释放锁的版本。
独占式释放锁
独占式释放锁是通过release()方法的独占式版本来实现的。当一个线程释放锁时,它需要首先释放当前线程持有的锁,然后唤醒等待队列中的下一个线程。如果等待队列中没有其他线程在等待锁,那么释放锁的操作就完成了。
共享式释放锁
共享式释放锁是通过releaseShared()方法的共享式版本来实现的。当一个线程释放锁时,它需要首先释放当前线程持有的锁,然后唤醒等待队列中的下一个线程。如果等待队列中还有其他线程在等待锁,那么继续唤醒下一个线程,直到所有等待线程都成功获取到锁为止。
等待队列的实现
等待队列的实现是AQS中的重点之一。在等待队列中,每个等待线程都会被封装成一个Node节点,节点中保存了等待线程的状态信息和等待时间等数据。等待队列中的节点是一个双向链表,每个节点都有一个前驱节点和一个后继节点。
等待队列中有两种类型的节点,一种是独占节点,另一种是共享节点。独占节点用于独占式获取锁,共享节点用于共享式获取锁。
当一个线程请求锁时,如果锁已经被占用,那么该线程将会被封装成一个节点,并加入到等待队列的末尾。在等待队列中,每个节点的状态有以下几种:
CANCELLED:表示该节点已经被取消,不再参与锁的获取。
SIGNAL:表示该节点需要被唤醒,但是它的前驱节点仍然在等待队列中。
CONDITION:表示该节点在条件队列中等待。
PROPAGATE:表示当前共享式节点需要向后传播唤醒信号。
在等待队列中,每个节点的状态是通过CAS方法来修改的。如果一个线程在等待队列中等待的时间太长,那么它就有可能被中断或者超时,这时候节点的状态会被设置为CANCELLED。
AQS的核心方法
AQS中定义了一系列核心方法,这些方法实现了锁的基本功能,包括获取锁、释放锁、等待锁、唤醒等待线程等操作。其中,acquire()和release()方法是独占式锁的核心方法,acquireShared()和releaseShared()方法是共享式锁的核心方法。
acquire()方法的伪代码如下:
public void acquire(int arg) {
if (tryAcquire(arg)) {
return;
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg);
}
acquire()方法首先尝试获取锁,如果成功则直接返回。否则,它将创建一个新的独占式节点,并将其加入到等待队列中。接着,它将调用acquireQueued()方法来使当前线程进入等待状态。
acquireShared()方法的伪代码如下:
public void acquireShared(int arg) {
if (tryAcquireShared(arg) >= 0) {
return;
}
acquireSharedInterruptibly(arg);
}
acquireShared()方法首先尝试获取共享式锁,如果成功则直接返回。否则,它将调用acquireSharedInterruptibly()方法来使当前线程进入等待状态。
release()方法的伪代码如下:
public boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0) {
unparkSuccessor(h);
}
return true;
}
return false;
}
release()方法首先尝试释放锁,如果成功则唤醒下一个等待线程。否则,它将返回false表示释放锁失败。
releaseShared()方法的伪代码如下:
public boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
releaseShared()方法首先尝试释放共享式锁,如果成功则唤醒下一个等待线程。
应用场景
AQS的应用场景非常广泛,例如Java中的ReentrantLock、Semaphore、CountDownLatch等类都是基于AQS实现的。此外,在并发编程中,AQS也被广泛应用于实现自定义的同步器。
AQS的另一个重要应用场景是线程池。Java中的线程池是通过ThreadPoolExecutor类来实现的,其中线程池的核心机制就是AQS。在线程池中,AQS用于管理线程的状态,控制线程的执行顺序和协调线程之间的同步。
总结
本文对AQS的源码分析进行了深入的讲解,详细介绍了AQS的基本原理、核心方法、等待队列的实现以及应用场景等方面的内容。
猜你喜欢
- 2024-10-30 读写锁,你难道不需要了解一下吗?
- 2024-10-30 谷歌云故障14个小时,系“队列突变大量积压”引起
- 2024-10-30 什么是AQS及其原理(aqs作用)
- 2024-10-30 码仔漫画:怎么给女朋友讲明白线程池?
- 2024-10-30 面试官:谈谈这4种磁盘IO调度算法--CFQ、NOOP、Deadline、AS
- 2024-10-30 QT的信号槽机制简介(qt信号槽优缺点)
- 2024-10-30 Java AQS(AbstractQueuedSynchronizer)详解
- 2024-10-30 AQS是什么(AQS是什么药品)
- 2024-10-30 详解磁盘IO调度算法--CFQ、NOOP、Deadline、AS
- 2024-10-30 基于AbstractQueuedSynchronizer的并发类实现
你 发表评论:
欢迎- 最近发表
-
- linux CentOS检查见后门程序的shell
- 网络安全工程师演示:黑客是如何使用Nmap网络扫描工具的?
- Linux中ftp服务修改默认21端口等(linux修改ftp配置文件)
- Linux系统下使用Iptables配置端口转发,运维实战收藏!
- 谈谈TCP和UDP源端口的确定(tcp和udp的端口号相同吗)
- Linux 系统 通过端口号找到对应的服务及相应安装位置
- 快速查找NAS未占用端口!Docker端口秒级排查+可视化占坑双杀技
- 【知识杂谈#2】如何查看Linux的(本地与公网)IP地址与SSH端口号
- 如何在Linux中查询 DNS 记录,这三个命令可谓是最常用、最经典的
- 【Linux系统编程】特殊进程之守护进程
- 标签列表
-
- 下划线是什么 (87)
- 精美网站 (58)
- qq登录界面 (90)
- nginx 命令 (82)
- nginx .http (73)
- nginx lua (70)
- nginx 重定向 (68)
- Nginx超时 (65)
- nginx 监控 (57)
- odbc (59)
- rar密码破解工具 (62)
- annotation (71)
- 红黑树 (57)
- 智力题 (62)
- php空间申请 (61)
- 按键精灵 注册码 (69)
- 软件测试报告 (59)
- ntcreatefile (64)
- 闪动文字 (56)
- guid (66)
- abap (63)
- mpeg 2 (65)
- column (63)
- dreamweaver教程 (57)
- excel行列转换 (56)
本文暂时没有评论,来添加一个吧(●'◡'●)