当前位置: 首页 > 后端技术 > Java

Java并发编程LockCondition&ReentrantLock(一)

时间:2023-04-01 16:12:58 Java

Lock框架为java并发编程提供了synchronized的替代方案。同步是一种隐式实现。底层封装了锁资源的获取和释放的所有实现细节。程序员不需要关心这些细节,也没有办法去关心。使用起来非常方便和安全。Lock由Java语言实现,公开了锁资源获取和释放的所有细节,在资源加锁过程中提供了更多的选择。获取锁资源后,可以通过Condition对象对锁资源进行细粒度管理。最重要的是Lock大量使用了CAS,充分利用了“持有锁的线程不会长期占用锁”的假设,尽可能先自旋再锁资源。所以Lock在多线程环境下应该比synchronized有更好的性能。基于Lock接口的ReentrantLock广泛应用于java线程池框架Executor中。掌握ReentrantLock是深入了解各种Executor(ThreadPoolExecutor、ScheduledThreadPoolExecutor等)和各种阻塞队列的必要前提。锁有排他锁和共享锁的区别。独占锁是指一个线程获取到锁资源后可以独占锁资源,其他线程只能等待。共享锁是指多个线程可以同时获取锁资源。今天我们的研究对象是ReentrantLock,ReentrantLock是独占锁,主要研究内容:ReentrantLock的基本概念,基本数据组织:AQS,CLH队列公平锁,非公平锁ConditionlockwithoutConditionparticipation,unlocklockwithConditionparticipation,unlockReentrantLockReentrantLock的基本概念ReentrantLock顾名思义就是“可重入锁”,意思是同一个线程可以多次获取锁,n次获取需要n次释放才能最终释放ReentrantLock。ReentrantLock的基本原理:与synchronized不同,ReentrantLock没有“锁对象”的概念,或者可以理解为锁对象就是ReentrantLock对象本身。ReentrantLock设置一个状态值,通过对状态值的原子操作实现锁资源的获取和释放,任何线程获取锁资源的充要条件是ReentrantLock处于空闲状态。同理,任何一个线程在获取到锁资源后,ReentrantLock都处于被占用状态。ReentrantLock最基本的两个操作:lock和unlock,lock获取锁资源,unlock释放锁资源ReentrantLock维护了一个CLH队列,CLH队列是一个先进先出的双向队列。当ReentrantLock处于空闲状态时,锁调用立即返回,调用线程获得锁资源。否则请求线程进入CLH队列等待被其他线程唤醒。获取锁资源的线程在业务执行完成后调用unlock释放锁资源,然后按照先进先出的原则唤醒最先进入队列的线程。被唤醒的线程继续执行锁操作,节点从CLH队列中出队并返回---表示请求锁资源的线程等待后成功获取到锁资源,继续上面第6步的逻辑是没有Condition对象参与的ReentrantLock的获取和释放逻辑比较简单。当涉及到Condition时,情况会稍微复杂一些:ReentrantLock对象可以通过newCondition()操作持有Condition对象,一个ReentrantLock可以持有多个Condition对象。Condition维护一个Condition队列。Condition的常见操作包括await、signal等,在执行操作时,假设当前线程已经获取了ReentrantLock锁资源。await操作会释放当前线程已经获取的ReentrantLock锁资源,挂起当前线程,并将当前线程加入Condition队列等待其他线程唤醒。比如DelayedWorkQueue的take方法中,如果当前DelayedWorkQueue队列为空,则take线程加入名为available的Condition排队等待。当相关操作可能导致Condition的条件满足时,调用Condition的signal方法唤醒并在Condition队列中等待。溃败。比如上例中DelayedWorkQueue的add方法完成后,会调用available信号方法唤醒在available队列中等待的线程。线程被唤醒后,从Condition队列中出队,进入ReentrantLock的CLH队列,等待锁资源的重新获取。条件示例:take方法中如果队列为空,则挂起等待如果你只是想对ReentrantLock有一个基本的了解,了解ReentrantLock的应用,而不是从源码的角度去深入研究,我个人认为掌握以上基本原理应该就够了,保证你能看懂ReentrantLock在阻塞队列和线程池的源码逻辑。但是如果你想完全理解ReentrantLock是如何实现上述逻辑的,还需要从源码的角度继续深入研究。ReentrantLock数据结构:当AQS和CLH队列中的多个线程同时竞争ReentrantLock锁资源时,只有竞争获胜的线程才能获得锁资源,其他线程只能排队等待。用于排队的队列是CLH队列,AQS(AbstractQueuedSynchronizer)是实现CLH队列的虚类。ReentrantLock有一个很重要的属性Sync。Sync是AQS的一个虚拟扩展类。Sync有两个实现类:NonfairSync和FairSync。类结构如下:NonfairSync和FairSync都是AQS的最终实现。AQS虚拟类是一个标准模板。定义了Lock(阻塞队列)的基本数据结构,实现了Lock的大部分功能。进入队列的线程被封装为Node。Node是AQS定义的一个内部类。是我们学习AQS首先要掌握的。Node的重要属性:waitStatus:等待状态,Node用于排队,waitStatus表示当前节点的等待状态,有以下几种等待状态:CANCELED=1:表示当前等待线程已经计算完毕SIGNAL=-1:表示节点在CLH队列中排队,等待出队CONDITION=-2:表示当前节点在Condition队列中,等待出队PROPAGATE=-3:会使用共享锁,不解析nowprev:上一个节点next:双向队列,当然必须有下一个节点Threadthread:节点的主角,排队线程nextWaiter:专用于Condition队列,用于指向同步队列(CLH)Condition队列的下一个节点AQS和Condition队列的节点。这个Node,所以Node类针对两者做了一部分兼容的设计,比如nextWaiter是针对Condtion队列的下一个节点,next是针对CLH的下一个节点。AQS的重要属性是state:lockstate,通过对state的原子操作实现对锁资源的控制:一个线程成功通过原子操作将状态从idle变为occupied,表示当前线程已经成功获取到锁资源。无法获取锁资源的线程被封装为Node节点,进入队列排队等待。head:第一个节点,头节点tail:尾节点通过head节点,tail节点,以及每个节点的prev和next实现一个双向队列。公平锁和非公平锁所谓公平锁和非公平锁是由Sync属性决定的:当Sync创建为NonfairSync时,是非公平ReentrantLock,否则是公平ReentrantLock。无参构造函数创建一个非公平ReentrantLock,参数构造函数ReentrantLock(booleanfair)可以通过参数指定创建公平锁还是非公平锁。当线程请求锁资源时,公平锁会检查CLH队列。如果队列不为空,则先入队。先申请的线程会先获得锁资源,所以是“公平”的锁。非公平锁在线程申请锁资源时不检查CLH队列,直接尝试获取锁资源,获取失败后进入队列。因此,请求线程会获得比队列中线程更高的优先级,这对队列中排队的线程是不公平的,因此称为非公平锁。ConditionCondition提供await和signal(及其变体)方法,为ReentrantLock锁资源提供更多选择:当前线程获取ReentrantLock锁资源后,可以通过Condition对象的await方法挂起当前线程,直到其他线程通过signal方法对象唤醒。一个ReentrantLock可以创建多个Condition对象,每个Condition对象都是独立的,互不影响。ReentrantLock就像街头的黑手党老大。黑道老大首先要拿下这条街,即获得ReentrantLock锁资源。后面的每一个Condition就像这条街上的餐厅A、小店B、公厕C,分别对应ConditionObjectA、ConditionObjectB、ConditionObjectC。如果已满,则必须通过调用ConditionObjectA的await方法进入ConditionObjectA的队列排队等待(当前线程在AQS中被封装为一个Node进入队列(假设叫NodeA),当前线程A被挂了),这时候黑道老大需要交出整条街的锁权限(好像不合理。。。),之后餐厅A有人吃完就离开店,会通知NodeA谁是通过ConditionObjectA的signal方法在队列中等待,所以NodeA会从ConditionObjectA的队列中出来,在ReentrantLock的CLH队列中排队,等待ReentrantLock锁资源被重新获取,然后唤醒线程A。如果其他这个过程中人(其他线程)要进入B店,需要操作的是店对应的ConditionObjectB,与餐厅对应的ConditionObjectA无关。在总结中发现开篇内容太多,篇幅有限。下面的“无Condition参与的加锁和解锁”和“有Condition参与的加锁和解锁”基本就是以上逻辑的源码分析,放在下一篇。多谢!上一篇周期性任务线程池——ScheduledThreadPoolExecutor&DelayedWorkQueue下一篇Java并发编程LockCondition&ReentrantLock(二)

猜你喜欢