1.AQS与ReentrantLock详解
2.ReentrantLock 源码解析 | 京东云技术团队
3.Linux:带你理解死锁(银行家算法详解)及死锁产生的死锁死锁必要条件
4.深入解析 go 互斥锁 mutex 源码
5.多线程死锁检测的分析与实现(linux c)-有向图的应用
6.慎用,Mybatis-Plus这个方法可能导致死锁
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的队列机制实现线程阻塞和唤醒。例如,2022进群暗雷源码NofairSync.lock展示了非公平锁的实现,涉及CAS(Compare and Swap)操作,保证了线程安全。Unsafe类在这其中发挥了关键作用,提供了低层次的内存操作,如CAS操作。
总结来说,ReentrantLock和AQS是Java并发编程中的重要基石,通过理解它们的工作原理,可以更好地应对并发环境中的问题。
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 是一个业务方法,里面并没有实际的业务处理,都是在调用其他方法。
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 功能更加丰富,支持公平锁实现,支持中断响应以及限时等待等,是处理并发问题很好的解决方案。
Linux:带你理解死锁(银行家算法详解)及死锁产生的必要条件
理解Linux中的死锁问题,特别是通过银行家算法来解决。死锁是一种程序执行过程中,多个线程因资源竞争而陷入无法继续的状态。死锁产生的犀牛黄金坑指标源码关键条件包括互斥、请求与保持、不可抢占和循环等待。要预防死锁,可以尝试破坏这些条件,如采用"按序分配"策略,避免形成循环等待,以及在资源分配时考虑系统的安全状态。
银行家算法的核心是,进程在运行前声明所需的资源数量,系统根据资源总量和进程声明进行分配。在分配过程中,系统会检查分配后的状态是否会导致安全问题,如死锁。若发现可能的死锁,算法会通过回溯资源分配或非阻塞加锁来解除死锁。在高性能应用中,无锁编程(如CAS锁、原子操作)是常用策略以提高程序性能。
举例来说,如果有五个线程和三种资源,死锁检测与银行家算法主要关注资源分配的顺序和安全状态。正确理解这些概念,可以帮助避免死锁,确保资源的有效使用。学习资料和内核技术交流群可以在获取,内核源码技术学习路线和视频教程等资源也一应俱全。
深入解析 go 互斥锁 mutex 源码
互斥锁是并发控制的基石,用于避免多线程竞争带来的数据不一致性问题。以加法运算为例,若不使用互斥锁,多个线程同时执行加法操作可能导致数据覆盖,结果不准确。互斥锁(Mutex)确保在同一时刻只有一个线程访问共享资源。
在互斥锁的源码解析中,我们关注几个核心问题:饥饿问题、性能优化、锁的创建与操作。
互斥锁通常会经历几代优化,以提升性能与公平性。例如,当一个线程在等待获取锁时,系统可能选择将锁直接分配给等待时间最长的线程(饥饿模式),以确保所有线程都有机会访问共享资源。在正常模式下,锁的分配遵循先入先出的原则,以提升性能。这些模式的选择和切换依赖于互斥锁内部的状态。
互斥锁的实现涉及位运算,如位与(&)、位或(|)、位异或(^)等操作。这些位操作用于管理锁的状态,如判断锁是否被持有、锁是否处于饥饿状态等。
在使用互斥锁时,需要注意几个常见错误:锁重入、锁拷贝和死锁。锁重入允许同一线程多次获取同一锁,无需阻塞。锁拷贝则涉及锁的复制,需确保复制时不破坏锁的状态。死锁是由于线程间循环等待资源而导致的僵局,需通过合理设计避免。
在并发编程中,正确使用互斥锁至关重要,需遵循“谁申请,谁释放”的原则,避免锁的不当释放导致的不可预期行为。对于更高级的锁机制,如自旋锁、阻塞锁和排他锁,它们在并发控制中发挥着不同的作用,提供了不同程度的性能优化和安全保证。
此外,信号量(semaphore)是一种常见的同步工具,用于协调并发操作。它提供了类似于互斥锁的功能,但允许更细粒度的控制,如允许多个读锁而只允许一个写锁。信号量的实现通常依赖于系统调用,如Linux的futex,或在Go中使用专门的同步库。
总体而言,互斥锁是并发编程中不可或缺的工具,正确理解和使用它们能够有效管理并发问题,确保程序的正确性和稳定性。
多线程死锁检测的分析与实现(linux c)-有向图的应用
在多线程编程中,死锁问题是个棘手难题。它源于资源争夺和进程推进顺序的不当,导致进程陷入僵局。当我们需要检测一个程序是否遭遇死锁,首先理解死锁的四个必要条件——互斥、请求保持、不剥夺和环路等待。死锁表现为线程间的资源循环等待,形成有向环形图结构。
死锁问题的根源在于资源竞争和进程顺序,例如,两个线程A和B分别按顺序争夺资源a和b,形成一个循环,进而影响其他线程。要解决这个问题,关键在于理解有向环的检测。当程序中出现死锁,就反映了线程间的锁关系形成了环形链,这可通过维护加锁和释放锁后有向图的状态来识别。
检测死锁的过程涉及三个步骤:在加锁前检查资源占用情况,加锁后建立线程和锁的对应关系,并在释放锁后更新图状态。利用Hook技术,我们可以自定义加锁和解锁函数,集成环形链检测逻辑,让开发者在常规操作中无需额外关注死锁问题。
在源代码实现中,会用到图的基本结构,如结点包含线程ID和锁ID,邻接表表示边,以及深度优先遍历(DFS)来检测环。通过深度遍历,如果遇到已访问过的结点,就说明存在死锁。编译并运行源代码(deadlock.c)如下:
gcc -o deadlock deadlock.c -lpthread -ldl;
总之,死锁问题的检测与解决涉及到对资源竞争、进程顺序和图论的理解,通过构建和维护有向图,可以有效地识别和预防多线程程序中的死锁。
慎用,Mybatis-Plus这个方法可能导致死锁
A同学在生产环境使用了Mybatis-Plus提供的 com.baomidou.mybatisplus.extension.service.IService#saveOrUpdate(T, com.baomidou.mybatisplus.core.conditions.Wrapper) 方法(以下简称B方法),并发场景下,数据库报了如下错误
死锁现象的原因分析
理论与实际操作相结合,确定死锁是由间隙锁导致的。
死锁定义为两个事务互相等待对方持有的锁,导致互相阻塞,从而导致死锁。间隙锁则是MySQL为避免幻读,向左找第一个比当前索引值小的值,向右找第一个比当前索引值大的值(没有则为正无穷),将此区间锁住,阻止其他事务在此区间插入数据。
MySQL引入间隙锁是为了与Record lock组合成Next-key lock,以便在可重复读这种隔离级别下避免幻读。
在并发场景下,事务一和事务二可能在对方持有的间隙锁上互相等待,导致死锁。
通过源码分析,发现B方法的逻辑不按照常规流程进行,先执行修改操作,而不是先查询。这可能导致在修改时增加了间隙锁,从而在并发场景下导致死锁。
通过验证间隙锁死锁的场景,可以清楚地看到锁类型是行锁,锁模式是间隙锁。
分析表明间隙锁加锁是非互斥的,即事务一对间隙A加锁后,事务二依然可以给间隙A加锁。
解决死锁的方法
推荐自定义saveOrUpdate方法,而非简单地关闭间隙锁。关闭间隙锁的方法仅适用于当前业务场景确实不关心幻读的问题。自定义方法可以避免Mybatis-Plus提供的方法中的额外反射操作和事务处理,减少额外开销。
拓展分析
如果两个事务修改的是存在的行,MySQL会使用Record Lock而非间隙锁,这与间隙锁不同,Record Lock是互斥的,即不同的事务不能同时对同一行记录添加Record Lock。
结论
虽然Mybatis-Plus提供的方法可能引起死锁,但它仍是一款优秀的增强框架,提供了高效的lambda写法,提高开发效率。在使用时,应秉持辩证态度,熟悉的方法大胆尝试,陌生的方法谨慎使用。