当前位置: 首页 > 网络应用技术

抽象队列同步器(独家锁)

时间:2023-03-06 14:50:10 网络应用技术

  JUC中的许多并发类都继承了AbstractQueueedsynchronizer(aqs),例如CountDownLatch,reentrantlock,threadlocalexcutor,等等。

  它主要实现同步状态和队列的管理,并等待阻止线程的通知。以Reetrantlock为例。它具有以下功能

  上面提到的写作函数取决于AQS,因为Reetrantlock只能通过一个线程获得,因此它是一个独家锁,并且在reentrantreadwritelock中可以通过多个线程共享readlock,这意味着它是它的。,这意味着它就是它,这意味着它就是它。

  因此,AQ中的内容可以分为四个部分

  

  节点是等待线的载体,即两个路线链接列表中线程所在的位置上的节点。

  抽象QueedSynchronizer中有很多方法,类似于在垄断和共享锁中“相似方法”中的TryAcquire和TryAcquireShared的不同实现。

  在下图中是AbstractQueedSynchronizer中的一些成员变量。其中,头部和尾巴都是代表团队和尾部节点的节点变量。状态代表同步状态。它是AbstractQuedsynchronizer.Class相对于AbstractQuedSynChronizer.Class.class..class.class.class.in的偏移,而NextOffset是相对于Node.Class.in的偏移量。

  在下面,我们使用几个示例来探索AQS中某些方法的实现,并在重新输入中发挥作用。

  让我们从简单的锁定和解锁实例开始

  通过断点,锁的特定实现是在nonfairsync内部类别的reentrantlock中,这是由于我们为锁定对象设置的非flat锁。

  该锁没有任何线程所拥有的,然后我们将其状态更改为1代表它。

  返回后,将AQS中的独家线程的字段分配给当前线程,然后锁成功。

  然后,我们输入重新进入的解锁方法。此方法的主要实现是在AQ的发行方法中

  然后输入TryRelease方法以获取当前的锁定状态,然后使用C表示要更改的目标状态。检查后,锁定线程为空,并将其状态字段状态修改为目标状态。这里的锁已经锁定在这里。

  在TryRelease返回True之后,它将确定AQ中没有等待节点。

  我们使用相同的重新输入锁执行两个锁定操作。由于第一个简单实例过程与上述相同,因此我们只注意第二个锁并解锁

  目前,由于它已经找到了一次,即状态= 1,因此compareAndsetState(0,1)将无法成功分配,因此它将输入获取方法,然后它将首先输入TryAcquire方法方法

  我们将在TryAcquire中再次判断锁(因为最后一次可以在过程中发布),然后由于当前线程是此锁的独家线程,我们可以重新组织此锁,最后状态将符合该锁定的值值2表示锁定由当前线重复两次。

  因为TryAcquire(1)返回真实!tryAcquire(1)是错误导致程序不输入“获取方法”中的以下执行过程,这意味着第二个锁已完成。

  就像简单示例中的解锁一样,程序将首先输入发布方法,然后输入TryRelease方法。然后因为更改后的更改是1

  重新竞争将涉及等待队列和等待节点的阻塞和觉醒,因此其一系列操作的复杂性高于上面的示例。使用以下示例来体验多线程竞争锁的过程。

  T1将首先锁定。此过程与无竞争锁的获取相同。主要区别是获取锁和T1释放锁的过程。

  在这个想法中,您可以在此位置切换调试线程

  T1线获得锁后,我们切换到T2线程,发现此时这个想法标记了我们。锁已被T1占据。

  然后它将输入获取方法。由于此时T1已占据锁,因此状态≠0的当前线程和锁定为T1≠T2,因此TryAcquire返回False,因此程序将输入AddWaiter方法。

  在此方法中,首先将T2螺纹封装到节点对象中,然后由尾部节点初始化队列。由于目前不存在CLH队列,因此它将输入ENQ方法以首次初始化的队列。

  初始化此队列在ENQ中,将初始化队列的队列,然后将传递节点插入团队末端。在这个地方,我们看到了(;;)的死周期?

  在此ENQ中,我实际上只执行了两次。首次将没有实际数据设置为HEAD节点的头节点可以在周期中完成,例如以下代码块中的实现方法

  实际上,旋转是为了确保线程的安全性。当获得T2螺纹时,也可能会有其他螺纹与锁定。例如,当线程在T2执行和头部之间初始化时,它已成功设置了头节点。返回false将不会输入此分支。目前,您将重新计入尾部节点,然后将传递节点节点插入下一个。

  在AddWaiter之后,它将在结束后输入帐户。此方法主要是为了制作锁定和块。最后,它基于失败的字段来确定是否取消获得线程。这种情况通常被置于取消状态

  应该parkafterfailedAcquire确定节点是否应阻止并等待。如果该节点是信号状态,则意味着应该阻止节点的后继器,然后将观察到ParkandCheckickInterupt方法,并且将确定唤醒时线是否中断。

  在正常情况下,如果T1线程未解锁,则T2螺纹将始终阻止ParkAndcheckirtrupr方法。当唤醒时,它将继续尝试获取锁。

  然后,我们切换回T1线程,输入解锁方法,调用AQS的发行方法,然后在TryRelease中的操作与上述两个实例相同。因此,我们不会唤醒封锁节点,因为此时我们将T2线程所在的节点存储在队列中,因此程序将输入UnparkSuccessor方法。方法执行后,状态唤醒了!

  T2唤醒后,我将再次去TryAcquire。成功后,转到关键区域内容,然后正常释放锁定锁。

  上面使用的重新输入洛克引入了AQS独家锁的相关内容。此外,它还将引入共享锁的实现,条件的实现以及通过ReentrantReadWritelock在其他相关JUC类中使用AQ。

  原始:https://juejin.cn/post/7100515072848398