1.AbstractQueuedSynchronizer详解
2.后端面经-JavaAQS详解
3.ReentrantLock 源码解析 | 京东云技术团队
4.ReentrantLock源码详细解析
5.AQS简简单单过一遍
6.AQS与ReentrantLock详解
AbstractQueuedSynchronizer详解
AQS,码讲 或者抽象队列同步器,是码讲一个Java并发工具类,它提供了一个框架,码讲用于构建依赖于先进先出(FIFO)等待队列的码讲阻塞锁和相关同步器(如信号量、事件等)。码讲这个核心类设计巧妙,码讲面相预测源码特别适合那些基于单个原子整数值表示状态的码讲同步器实现。它的码讲主要作用是在并发编程中处理资源同步访问,解决资源竞争时的码讲等待和唤醒问题,比如ReentrantLock、码讲ReentrantReadWriteLock和ArrayBlockingQueue等都是码讲基于AQS构建的。
没有AQS的码讲情况下,实现锁需要考虑等待和唤醒机制,码讲以及状态的码讲转换。相比之下,码讲AQS简化了这些步骤,它自身主要关注状态的等待和释放,具体到锁的获取和释放(tryAcquire和tryRelease方法),以及锁的公平性逻辑。子类则负责定义状态和控制获取/释放的行为。例如,ReentrantLock使用state值表示锁的持有状态,公平锁(如NonfairSync)的实现则涉及到acquire和release方法的调用,以及队列的使用来实现阻塞和唤醒。
理解AQS的核心流程,有助于我们更好地实现并发控制。然而,为了便于解释,实际的源码实现可能包含更多细节和优化。初学者在掌握基本原理后,深入阅读源码会更为高效。总的来说,AQS是并发编程中一个强大的工具,通过tryAcquire和tryRelease的配合,实现了锁的高效管理和同步控制。
后端面经-JavaAQS详解
AQS是什么?
AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的diagram源码ReentrantLock。简单来说,AQS定义了一套框架,来实现同步类。
AQS的核心思想是对于共享资源,维护一个双端队列来管理线程,队列中的线程依次获取资源,获取不到的线程进入队列等待,直到资源释放,队列中的线程依次获取资源。AQS的基本框架如图所示:
资源state变量表示共享资源,通常是int类型。CLH双向队列是一种基于逻辑队列非线程饥饿的自旋公平锁,具体介绍可参考此篇博客。CLH中每个节点都表示一个线程,处于头部的节点获取资源,而其他资源则等待。Node的方法和属性值如图所示:其中,
一般来说,一个同步器是资源独占模式或者资源共享模式的其中之一,因此tryAcquire(int)和tryAcquireShared(int)只需要实现一个即可,tryRelease(int)和tryReleaseShared(int)同理。但是同步器也可以实现两种模式的资源获取和释放,从而实现独占和共享两种模式。
acquire(int)是获取资源的顶层入口,tryAcquire(int)是获取资源的方法,需要自定义同步器实现。addWaiter(Node.EXCLUSIVE)是将线程加入等待队列的尾部,acquireQueued(Node node, int arg)将线程阻塞在等待队列中,直到获取到资源后才返回。
release(int)是释放资源的顶层入口方法,tryRelease(int)是释放资源的方法,需要自定义同步器自己实现。unparkSuccessor(h)是唤醒后继节点的方法。
acquireShared(int)和releaseShared(int)是使用共享模式获取共享资源的顶层入口方法,tryAcquireShared(arg)是获取共享资源的方法,doAcquireShared(arg)将线程阻塞在等待队列中,直到获取到资源后才返回。releaseShared(int)是primer源码释放共享资源的顶层入口方法,doReleaseShared()方法释放共享资源。
面试问题模拟:AQS是接口吗?有哪些没有实现的方法?看过相关源码吗?
A:AQS定义了一个实现同步类的框架,实现方法主要有tryAquire和tryRelease,表示独占模式的资源获取和释放,tryAquireShared和tryReleaseShared表示共享模式的资源获取和释放。源码分析如上文所述。
ReentrantLock 源码解析 | 京东云技术团队
并发指同一时间内进行了多个线程。并发问题是多个线程对同一资源进行操作时产生的问题。通过加锁可以解决并发问题,ReentrantLock 是锁的一种。
1 ReentrantLock
1.1 定义
ReentrantLock 是 Lock 接口的实现类,可以手动的对某一段进行加锁。ReentrantLock 可重入锁,具有可重入性,并且支持可中断锁。其内部对锁的控制有两种实现,一种为公平锁,另一种为非公平锁.
1.2 实现原理
ReentrantLock 的实现原理为 volatile+CAS。想要说明 volatile 和 CAS 首先要说明 JMM。
1.2.1 JMM
JMM (java 内存模型 Java Memory Model 简称 JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量的访问方式.
由于 JMM 运行的程序的实体是线程。而每个线程创建时 JMM 都会为其创建一个自己的工作内存 (栈空间), 工作内存是每个线程的私有数据区域。而 java 内存模型中规定所有的变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问,但线程的变量的操作 (读取赋值等) 必须在自己的工作内存中去进行,首先要将变量从主存拷贝到自己的工作内存中,然后对变量进行操作,操作完成后再将变量操作完后的新值写回主内存,不能直接操作主内存的变量,各个线程的工作内存中存储着主内存的变量拷贝的副本,因不同的线程间无法访问对方的工作内存,线程间的通信必须在主内存来完成。
如图所示:线程 A 对变量 A 的操作,只能是smbc源码从主内存中拷贝到线程中,再写回到主内存中。
1.2.2 volatile
volatile 是 JAVA 的关键字用于修饰变量,是 java 虚拟机的轻量同步机制,volatile 不能保证原子性。 作用:
作用:CAS 会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读 - 改 - 写操作。
1.2.4 AQSAQS 的全称是 AbstractQueuedSynchronizer(抽象的队列式的同步器),AQS 定义了一套多线程访问共享资源的同步器框架。
AQS 主要包含两部分内容:共享资源和等待队列。AQS 底层已经对这两部分内容提供了很多方法。
2 源码解析
ReentrantLock 在包 java.util.concurrent.locks 下,实现 Lock 接口。
2.1 lock 方法
lock 分为公平锁和非公平锁。
公平锁:
非公平锁:上来先尝试将 state 从 0 修改为 1,如果成功,代表获取锁资源。如果没有成功,调用 acquire。state 是 AQS 中的一个由 volatile 修饰的 int 类型变量,多个线程会通过 CAS 的方式修改 state,在并发情况下,只会有一个线程成功的修改 state。
2.2 acquire 方法
acquire 是一个业务方法,里面并没有实际的业务处理,都是在调用其他方法。
2.3 tryAcquire 方法
tryAcquire 分为公平和非公平两种。
公平:
非公平:
2.4 addWaiter 方法
在获取锁资源失败后,需要将当前线程封装为 Node 对象,并且插入到 AQS 队列的末尾。
2.5 acquireQueued 方法
2.6 unlock 方法
释放锁资源,将 state 减 1, 如果 state 减为 0 了,唤醒在队列中排队的 Node。
3 使用实例
3.1 公平锁
1. 代码:
2. 执行结果:
3. 小结:
公平锁可以保证每个线程获取锁的机会是相等的。
3.2 非公平锁
1. 代码:
2. 执行结果:
3. 小结:
非公平锁每个线程获取锁的机会是随机的。
3.3 忽略重复操作
1. 代码:
2. 执行结果:
3. 小结:
当线程持有锁时,不会重复执行,可以用来防止定时任务重复执行或者页面事件多次触发时不会重复触发。gradleidea源码
3.4 超时不执行
1. 代码:
2. 执行结果:
3. 小结:
超时不执行可以防止由于资源处理不当长时间占用资源产生的死锁问题。
4 总结
并发是现在软件系统不可避免的问题,ReentrantLock 是可重入的独占锁,比起 synchronized 功能更加丰富,支持公平锁实现,支持中断响应以及限时等待等,是处理并发问题很好的解决方案。
ReentrantLock源码详细解析
在深入解析ReentrantLock源码之前,我们先了解ReentrantLock与同步机制的关系。ReentrantLock作为Java中引入的并发工具类,由Doug Lea编写,相较于synchronized关键字,它提供了更为灵活的锁管理策略,支持公平与非公平锁两种模式。AQS(AbstractQueuedSynchronizer)作为实现锁和同步器的核心框架,由AQS类的独占线程、同步状态state、FIFO等待队列和UnSafe对象组成。AQS类的内部结构图显示了其组件的构成。在AQS框架下,等待队列采用双向链表实现,头结点存在但无线程,T1和T2节点中的线程可能在自旋获取锁后进入阻塞状态。
Node节点作为等待队列的基本单元,分为共享模式和独占模式,值得关注的是waitStatus成员变量,它包含五种状态:-3、-2、-1、0、1。本文重点讨论-1、0、1状态,-3状态将不涉及。非公平锁与公平锁的差异在于,非公平锁模式下新线程可直接尝试获取锁,而公平锁模式下新线程需排队等待。
ReentrantLock内部采用非公平同步器作为其同步器实现,构造函数中根据需要选择非公平同步器或公平同步器。ReentrantLock默认采用非公平锁策略。非公平锁与公平锁的区别在于获取锁的顺序,非公平锁允许新线程跳过等待队列,而公平锁严格遵循队列顺序。
在非公平同步器的实例中,我们以T1线程首次获取锁为例。T1成功获取锁后,将exclusiveOwnerThread设置为自身,state设置为1。紧接着,T2线程尝试获取锁,但由于state为1,获取失败。调用acquire方法尝试获得锁,尝试通过tryAcquire方法实现,非公平同步器的实现调用具体逻辑。
在非公平锁获取逻辑中,通过CAS操作尝试交换状态。交换成功后,设置独占线程。当当前线程为自身时,执行重入操作,叠加state状态。若获取锁失败,则T2和T3线程进入等待队列,调用addWaiter方法。队列初始化通过enq方法实现,enq方法中的循环逻辑确保线程被正确加入队尾。新线程T3调用addWaiter方法入队,队列初始化完成。
在此过程中,T2和T3线程开始自旋尝试获取锁。若失败,则调用parkAndCheckInterrupt()方法进入阻塞状态。在shouldParkAfterFailedAcquire方法中,当前驱节点等待状态为CANCELLED时,方法会找到第一个非取消状态的节点,并断开取消状态的前驱节点与该节点的连接。若T5线程加入等待队列,T3和T4线程因为自旋获取锁失败进入finally块调用取消方法,找到等待状态不为1的节点(即T2),断开连接。
理解了shouldParkAfterFailedAcquire方法后,我们关注acquireQueued方法的实现。该方法确保线程在队列中正确释放,如果队列的节点前驱为head节点,成功获取锁后,调用setHead方法释放线程。setHead方法通过CAS操作更新head节点,释放线程。acquire方法中的阻塞是为防止线程在唤醒后重新尝试获取锁而进行的额外阻断。
锁的释放过程相对简单,将state减至0,将exclusiveOwnerThread设置为null,完成锁的释放。通过上述解析,我们深入理解了ReentrantLock的锁获取、等待、释放等核心机制,为并发编程提供了强大的工具支持。
AQS简简单单过一遍
回顾前面,AQS的概念被引入,虽然在面试题中有所接触,但对其具体含义并不明晰。本篇内容旨在普及AQS的相关知识,至少能对其概念有一个清晰的认识。
AQS是java.util.concurrent包下核心类AbstractQueuedSynchronizer的缩写。它作为基础,支撑了诸如ReentrantLock、ReentrantReadWriteLock等锁的实现。
通读AbstractQueuedSynchronizer的注释,可以总结出它主要包含状态和队列两个关键元素。AQS通过volatile关键字保证状态的线程可见性,使用CAS算法修改状态值。状态值表示锁的持有情况。
AQS中的队列被称为CLH队列,是一个双向队列,用于管理等待获取锁的线程。队列的组成包括节点结构,每个节点包含状态值、前驱和后继节点的引用。
在AQS中,acquire和release方法负责获取和释放锁。acquire方法尝试获取锁,若失败,将线程插入等待队列;在队列中,线程会根据前置节点状态判断是否继续获取,直到获取锁或被中断。release方法唤醒后继节点,若后继节点满足释放条件,则唤醒;否则,从队列尾部向前寻找合适的节点,唤醒符合条件的节点。
总结而言,AQS是通过状态和队列机制管理锁的获取和释放,支持线程的等待和唤醒,实现线程安全的同步操作。有兴趣的读者可以深入源码和相关资源进行学习,本文旨在提供一个简单的概览。
AQS与ReentrantLock详解
J.U.C包中的Java.util.concurrent是一个强大的并发工具库,包含多种处理并发场景的组件,如线程池、队列和同步器等,由知名开发者Doug Lea设计。本文将深入讲解Lock接口及其关键实现ReentrantLock,它在并发编程中的重要性不可忽视,因为大部分J.U.C组件都依赖于Lock来实现并发安全。
Lock接口的出现,弥补了synchronized在某些场景中的不足,提供了更灵活的并发控制。ReentrantLock作为Lock的一种实现,支持重入,即同一线程可以多次获取锁而不必阻塞。这种特性在处理多方法调用场景时避免了死锁问题。
ReentrantReadWriteLock则允许读写操作并发进行,提高了读操作的并发性,避免了写操作对读写操作的阻塞,适用于读多写少的场景。在内存缓存示例中,读写锁通过HashMap以读写锁保护,确保并发访问的线程安全。
ReentrantLock的实现核心是AQS(AbstractQueuedSynchronizer),它是Lock实现线程同步的核心组件。AQS提供了独占和共享锁两种功能,如ReentrantLock就基于AQS的独占模式。AQS内部维护了一个volatile状态变量,不同的实现类根据其具体需求定义其含义。
ReentrantLock的源码分析中,我们看到lock()方法如何通过AQS的队列机制实现线程阻塞和唤醒。例如,NofairSync.lock展示了非公平锁的实现,涉及CAS(Compare and Swap)操作,保证了线程安全。Unsafe类在这其中发挥了关键作用,提供了低层次的内存操作,如CAS操作。
总结来说,ReentrantLock和AQS是Java并发编程中的重要基石,通过理解它们的工作原理,可以更好地应对并发环境中的问题。
Java并发系列 | Semaphore源码分析
在Java并发编程中,Semaphore(信号量)是AQS共享模式的实用工具,它能够控制多个线程对共享资源的并发访问,实现流量控制。Semaphore的核心概念是“许可证”,类似于公共汽车票,只有获取到票的线程才能进行操作。许可证数量有限,当数量耗尽时,后续线程需要等待,直到有线程释放其许可证。Semaphore构造器接受初始许可证数量,可以选择公平或非公平的获取方式。
Semaphore提供了获取和释放许可证的API,默认每次操作一个许可证。获取许可证有直接和尝试两种方式,直接获取可能阻塞,而尝试不会。acquire方法内部调用的是AQS的acquireSharedInterruptibly,它会尝试公平或非公平地获取,并在获取失败时决定是否阻塞。释放许可证则直接调用AQS的releaseShared方法,通过自旋循环确保同步状态的正确更新。
Semaphore的应用广泛,本文通过实现一个简单的数据库连接池,展示了Semaphore如何控制连接的并发使用。连接池初始化时创建固定数量的连接,每次线程请求连接时需要获取许可证,释放连接时则释放许可证。测试结果验证了Semaphore有效管理连接并发并确保了流量控制。
代码示例与测试结果表明,Semaphore通过控制许可证数量,确保了资源使用的合理调度,当连接池中所有连接被占用,后续请求将被阻塞,直到有连接被释放。这清楚地展示了Semaphore在并发控制中的作用。