1.ReentrantLock源码详细解析
2.ReentrantLock 源码解析 | 京东云技术团队
3.信号量(Semaphore)从入门到源码精通
4.Java并发系列 | Semaphore源码分析
5.17.AQS中的码找Condition是什么?
6.AbstractQueuedSynchronizer详解
ReentrantLock源码详细解析
在深入解析ReentrantLock源码之前,我们先了解ReentrantLock与同步机制的码找关系。ReentrantLock作为Java中引入的码找并发工具类,由Doug Lea编写,码找相较于synchronized关键字,码找它提供了更为灵活的码找grafana 源码解读锁管理策略,支持公平与非公平锁两种模式。码找AQS(AbstractQueuedSynchronizer)作为实现锁和同步器的码找核心框架,由AQS类的码找独占线程、同步状态state、码找FIFO等待队列和UnSafe对象组成。码找AQS类的码找内部结构图显示了其组件的构成。在AQS框架下,码找等待队列采用双向链表实现,码找头结点存在但无线程,码找T1和T2节点中的线程可能在自旋获取锁后进入阻塞状态。
Node节点作为等待队列的基本单元,分为共享模式和独占模式,值得关注的是waitStatus成员变量,它包含五种状态:-3、-2、-1、0、1。本文重点讨论-1、0、1状态,-3状态将不涉及。非公平锁与公平锁的差异在于,非公平锁模式下新线程可直接尝试获取锁,而公平锁模式下新线程需排队等待。
ReentrantLock内部采用非公平同步器作为其同步器实现,构造函数中根据需要选择非公平同步器或公平同步器。ReentrantLock默认采用非公平锁策略。非公平锁与公平锁的区别在于获取锁的顺序,非公平锁允许新线程跳过等待队列,而公平锁严格遵循队列顺序。
在非公平同步器的openstack计算源码实例中,我们以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),断开连接。git存放源码
理解了shouldParkAfterFailedAcquire方法后,我们关注acquireQueued方法的实现。该方法确保线程在队列中正确释放,如果队列的节点前驱为head节点,成功获取锁后,调用setHead方法释放线程。setHead方法通过CAS操作更新head节点,释放线程。acquire方法中的阻塞是为防止线程在唤醒后重新尝试获取锁而进行的额外阻断。
锁的释放过程相对简单,将state减至0,将exclusiveOwnerThread设置为null,完成锁的释放。通过上述解析,我们深入理解了ReentrantLock的锁获取、等待、释放等核心机制,为并发编程提供了强大的工具支持。
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 的操作,只能是从主内存中拷贝到线程中,再写回到主内存中。
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 是一个业务方法,里面并没有实际的业务处理,都是duilib源码剖析在调用其他方法。
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. 小结:
当线程持有锁时,不会重复执行,可以用来防止定时任务重复执行或者页面事件多次触发时不会重复触发。
3.4 超时不执行
1. 代码:
2. 执行结果:
3. 小结:
超时不执行可以防止由于资源处理不当长时间占用资源产生的死锁问题。
4 总结
并发是现在软件系统不可避免的问题,ReentrantLock 是可重入的独占锁,比起 synchronized 功能更加丰富,支持公平锁实现,支持中断响应以及限时等待等,是处理并发问题很好的解决方案。
信号量(Semaphore)从入门到源码精通
Semaphore是一个用于同步的工具类,在并发编程中扮演重要角色。PV操作是Semaphore的核心操作,P代表获取许可,V代表释放许可。P操作会检查许可是否可用,若可用则获取并返回,否则阻塞直到许可可用;V操作则释放一个许可。
Semaphore的使用场景主要涉及线程间的同步,比如在资源有限的情况下控制多个线程对资源的访问。例如,当一个队列只允许一定数量的线程同时访问时,可以使用Semaphore来限制队列访问的线程数量。
使用Semaphore的方式是通过构造方法创建实例,然后使用acquire和release方法来控制许可的获取和释放。acquire方法接受一个参数,表示需要获取的许可数量,默认为1,如果当前可用许可数不足,线程将阻塞直到许可可用。release方法则释放指定数量的许可,通常为1。
要深入理解Semaphore源码,建议从AQS(AbstractQueuedSynchronizer)的基础开始。AQS提供了对同步器的基本抽象,Semaphore正是基于AQS实现的一种同步工具。
对于具体源码分析,可以重点关注Semaphore的构造方法、获取锁方法(acquire)以及释放锁方法(release)。在源码中,acquire方法通过调用tryAcquireShared方法尝试获取许可,如果成功则返回true,否则阻塞直到许可可用。release方法则简单地调用releaseShared方法释放一个许可。
深入学习Semaphore和并发编程,可以参考内核技术中文网的相关资源。该网站提供了一些学习资料和交流社区,包括Linux内核源码学习路线、视频教程、电子书以及实战项目和代码等。此外,网站还定期更新内核技术资料包,供学习者免费获取。
Java并发系列 | Semaphore源码分析
在Java并发编程中,Semaphore(信号量)是AQS共享模式的实用工具,它能够控制多个线程对共享资源的并发访问,实现流量控制。Semaphore的核心概念是“许可证”,类似于公共汽车票,只有获取到票的线程才能进行操作。许可证数量有限,当数量耗尽时,后续线程需要等待,直到有线程释放其许可证。Semaphore构造器接受初始许可证数量,可以选择公平或非公平的获取方式。
Semaphore提供了获取和释放许可证的API,默认每次操作一个许可证。获取许可证有直接和尝试两种方式,直接获取可能阻塞,而尝试不会。acquire方法内部调用的是AQS的acquireSharedInterruptibly,它会尝试公平或非公平地获取,并在获取失败时决定是否阻塞。释放许可证则直接调用AQS的releaseShared方法,通过自旋循环确保同步状态的正确更新。
Semaphore的应用广泛,本文通过实现一个简单的数据库连接池,展示了Semaphore如何控制连接的并发使用。连接池初始化时创建固定数量的连接,每次线程请求连接时需要获取许可证,释放连接时则释放许可证。测试结果验证了Semaphore有效管理连接并发并确保了流量控制。
代码示例与测试结果表明,Semaphore通过控制许可证数量,确保了资源使用的合理调度,当连接池中所有连接被占用,后续请求将被阻塞,直到有连接被释放。这清楚地展示了Semaphore在并发控制中的作用。
.AQS中的Condition是什么?
Condition是Java中的接口,提供了与Object#wait和Object#notify相同的功能。它允许线程在特定条件满足时暂停执行,并在条件满足时被唤醒。Condition接口主要提供了两个方法:等待(await)和唤醒(signal)。与Object的等待与唤醒机制相似,Condition接口在实现上有所不同。它支持创建多个等待队列,即同一把锁可以拥有多个等待队列,线程在不同队列中等待。在实际使用中,需要先获取锁,然后调用Condition#await和Condition#signal方法实现等待与唤醒功能。Condition的源码分析显示,其通过双向链表实现等待队列,等待队列中的线程在调用await方法时,会释放锁并进入等待队列。当有线程调用signal或signalAll方法时,队列中的线程会被唤醒。理解Condition的实现原理有助于更高效地使用并发工具。
AbstractQueuedSynchronizer详解
AbstractQueuedSynchronizer,简称AQS,究竟是什么呢?
它提供了一个框架,用于实现基于先进先出(FIFO)等待队列的阻塞锁及相关同步器(如信号量、事件等)。这个类的设计目的是为了成为那些依赖单一原子整数值来表示状态的同步器的有效基础。
从JDK的注释来看,AQS是一个实现阻塞锁和相关同步器的框架,它基于先进先出的等待队列。这个类适用于大多数使用整数来表示状态的同步器。这里提到的框架实际上是一个类。
那么,在什么情况下我们会使用AQS呢?
在编程中,我们经常遇到并发问题。有了并发,就涉及资源共享;而资源共享又需要处理资源的同步访问。在处理同步时,我们需要解决竞争发生时的等待问题和竞争解除后的唤醒问题。AQS就是这样一个便于实现这种同步机制的框架。我们日常使用的ReentrantLock、ReentrantReadWriteLock以及ArrayBlockingQueue等都是基于AQS实现的。
如果没有AQS,又会如何呢?
在没有AQS的情况下,要实现锁需要怎么做呢?这些问题都是我们在实现锁时需要考虑的。
在AQS的情况下,情况又是怎样的呢?
AQS作为基础类,主要解决了在锁不可用时的等待,以及锁释放后的唤醒。锁状态的设定、如何获取锁以及如何释放锁,都是需要相应的同步机制自己实现的。因此,在使用AQS时,需要实现以下方法:
类似于ReentrantLock,使用state来标识锁的状态,state = 0表示锁未被获取,当state > 0表示锁已被获取。此外,实现了AQS的tryAcquire()和tryRelease()来处理state的状态,以处理锁的状态。这样就可以实现基础的ReentrantLock。因此,在AQS的支持下,实现类似的阻塞锁非常方便。
以上我们介绍了AQS是什么,以及AQS的具体使用场景,下面我们详细介绍AQS的具体实现机制。
我们先简单看一下AQS的核心流程。
接下来,我们将结合ReentrantLock来详细查看基于AQS如何实现排他锁。
首先,我们来看一下ReentrantLock是如何使用int类型的state来表示锁的状态的。state = 0表示锁未被获取。state > 0表示锁已被持有。由于是可重入锁,state表示了这个锁被同一个线程获取了多少次。
上面的代码片段是ReentrantLock的核心代码,为了便于解释,省略了其他代码。首先我们可以看到,在使用AQS时,通常不是直接实现AQS,而是创建一个内部的辅助类。
Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class
然后加锁和释放锁对应的lock和unlock,直接调用辅助类的lock和unlock。
下面我们来看一下Sync类的代码。
上面我们来看一下非公平锁NonfairSync的实现。
下面我们来看一下AQS中的acquire方法。
上面我们看了获取锁的方法acquire,下面看下释放锁的release。
总结一下,AQS通过tryAcquire以及tryRelease两个方法,来进行锁的获取以及释放,两个都是非阻塞的方法。如果获取成功则返回,如果获取失败,则添加队列。在队列中获取锁,获取失败则挂起,等待锁被释放后被重新唤醒。唤醒后还是会去尝试获取锁。释放锁的时候如果检查到已经全部释放,则会唤醒被挂起的线程。这样通过tryAcquire和tryRelease,实现了锁的获取以及等待,以及锁的释放。具体锁状态的控制,子类则是通过tryAcquire和tryRelease进行控制的。
在介绍ReentrantLock时,我们简化了很多代码。了解基本原理后,再去读源码会事半功倍。
2024-12-24 11:06
2024-12-24 10:45
2024-12-24 10:44
2024-12-24 10:16
2024-12-24 10:01
2024-12-24 09:34
2024-12-24 08:47
2024-12-24 08:40