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

打开实施重新输入锁锁的原则

时间:2023-03-08 22:22:37 网络应用技术

  作者|江宗资料来源|阿里技术公共帐户

  编写Java代码的学生知道,Java的锁中有两个类别,一个是同步锁,另一个是并发袋中的锁(JUC锁)(JUC锁定)。它们在它们中,同步锁是由Java语言级别。它不会在这里扩展。本文主要讨论JUC中的重新进入锁。

  1个摘要的共同体

  诸如reentrantlock的锁(),unlock()和其他API之类的API实际上取决于内部同步器(请注意,未同步)。Synchronizer分为Fairsync和nonnoffairsync。顾名思义,它指的是公平和非事实。

  当锁定方法的锁定方法重新输入时,实际上简单地将其传输到lock()方法的锁()方法,简单地传输到同步器:

  那么,这是什么同步?我们看到从抽象表格的同步器(aqs)继承的同步。AQS是并发包装的基石。AQS本身不会实现任何同步接口(例如锁,解锁,倒计时等),但它定义了并发资源控制逻辑的框架(使用并发资源控制逻辑(使用USETHE模板方法设计模式),它定义了独家土地获取和发布资源的获取和发布方法,以及收购和收购方法的共享土地获取和发行资源的发行方法。用于实现倒计时,信号量。例如,获取的框架如下:

  总体逻辑是,如果您曾经去过TryAcquire,那么如果成功,那就可以了。呼叫者继续执行他背后的代码。如果失败了,请执行AddWaiter和Accaperqueed.hof tryAcquire(),需要根据自己的同步需求来实现子类,而Accaliequeared()和AddWaiter()和AddWaiter()已由AQ实现。当前螺纹到AQS内部同步队列的尾部,而AcquireQueump的作用是在TryAcquire()失败时阻止当前线程。

  AddWaiter的代码如下:

  ENQ(节点)的代码如下:

  执行附加器后,同步队列的结构如下:

  获取守则如下:

  获得的逻辑是:

  确定您是否是同步队列中的第一个节点,然后尝试锁定。如果成功,将自己变成头节点。该过程如下:

  如果您不是排队或TryAcquire失败的第一个节点,请致电swardparkafterfailedacquire。主要逻辑是使用CAS将节点状态设置为信号,表明当前线程被阻塞并等待信号唤醒。如果设置失败,请继续在AcquireQuiredept中的死循环中重试,直到设置为设置为成功,然后调用ParkAndcheckirtrubl方法。ParkAndCheckInterrupt的作用是阻止当前线程并等待唤醒。ParkAndCheckInterrupt的实现需要使用下层的能力。这是本文的重点,要在下面的层中解释层。

  2重新进入

  让我们看一下Reentrantlock的基于AbstractQuesynchronizer的方式。

  Reentrantlock内使用的Fairsync和Nonfairsync。它们是AQ的子类,例如Fairsync的主要代码:如下:

  AQS中最重要的领域是状态。围绕该领域对锁和同步线的实现进行修订。AQ可以意识到各种不同的锁和同步器的原因之一是,可以根据自己的需求和同步状态的含义对不同的锁或同步线进行不同的定义,并且可以根据自己的需求和重写相应的TryAcquire,TryRelease或TryAcquireSharedWuling以操作同步状态。

  让我们看一下Fairsync的Fairsync的Fairsync的逻辑:

  在这一点上,Java级别的实现基本上很清楚。总结,整个框架如下所示:

  关于解锁的实施,它仅限于空间,并且不会讨论。以下重点是如何在锁定过程中阻止当前线程,即如何实现图中的unsafe.park()。

  unsafe.park and unsafe.unpark是sun.misc.unsafe类的本机方法,

  这两种方法的实现在JVM的Hotspot/src/sharc/share/vm/prims/unsafe.cpp文件中,

  核心是逻辑是线程 - > parker() - > park(isabsolute!= 0,time);是获取Java线程的Parker对象,然后执行其公园方法。每个Java线程具有Parker实例,并且定义了Parker类:

  公园方法:

  Park的想法:Parker_Counter内部有一个关键字段,该柜台用于记录SO所谓的“许可证”。当_counter大于0时,这意味着有许可证,然后您可以将_counter设置为0,即使您得到了它,即使您得到了它,即使您得到它,也可以得到它,也可以得到它,即使您获得Itpermit可以在运行后继续执行代码。如果目前的_counter不大于0,请等待此情况满足。

  让我看一下公园的具体实施:

  因此,从本质上讲,通过pthread库pthread_cond_t.let的条件变量来实现locksupport.park。查看如何实现pthread_cond_t。

  pthread_cond_t典型用法如下:

  关键是:PTHREAD_COND_WAIT和PTHREAD_COND_SIGNAL必须首先才能首先pthread_mutex_lock.slock.thing此保护,可能会生成种族条件并且信号丢失。通过pthread_cond_signal或pthread_cond_broadcast,线程醒来并返回pthread_cond_wait(),线程会自动获得sutex。

  整个过程如下图所示:

  1 pthread_mutex_lock

  例如,在Linux中,一个称为futex的系统(简单地称为快速用户空间的互操作性)。

  在此系统中,用户空间中的互斥变量执行原子增量和测试操作。

  如果操作结果表明锁上没有争议,则PTHREAD_MUTEX_LOCK的呼叫将返回,并且无需将上下文切换到内核,因此获得相互量的操作可以非常快。

  只有在检测到争议时,系统调用(称为futex)就会发生,并且上下文切换到内核,这将使呼叫过程输入睡眠状态,直到发布为止。

  有很多细节,尤其是用于可靠性和/或优先级继承,但这是它的本质。

  nptl/pthread_mutex_lock.c

  pthread_mutex_t定义如下:

  __kind字段指的是锁的类型,如下所示:如下:

  在:

  Mutex默认使用pthread_mutex_timed_np,因此我将步行到lll_mutex_lock_optimized。这是一个宏:

  由于它不是lll_private,因此walk lll_lock,lll_lock也是一个宏:

  请注意,futex出现在此处,本文的后续内容主要是围绕它的。

  其中,atomic_compare_and_exchange_bool_acq正在尝试尝试__futex(即mutex-> __ data .__ lock)通过原子操作到1。如果失败,请致电__lll_lock_wait,并且代码为:如下:如下:

  让我在这里解释,pthread将futex的锁状态定义为三种类型:

  因此,在锁未能输入__lll_lock_wait之后,首先确定futex是否等于2。如果这意味着每个人都在排队,您也可以排队(直接跳到futex_wait)。如果它不等于2您是第一个竞争的人,将futex设置为2,告诉后来排队的人,然后以身作则。Futex_Wait实际上称为futex System Call。在第四季度,让我们仔细分析该系统。

  2 PTHREAD_COND_WAIT

  本质也适用于futex系统调用,并且仅限于长度。

  为什么有futex,它解决了什么问题?您什么时候加入内核?

  简而言之,Futex的解决方案是:在用户空间中完全执行无竞争的操作,不需要系统调用,并且只有当竞争是偶然完成相应处理(等待或唤醒)的竞争时,Futex是一种同步机制对于用户模式和内核模式。它需要两种模式才能完成。futex变量位于用户空间而不是内核对象中。Futex的代码也分为两个部分:用户模式和内核模式。在没有竞争的情况下,在用户模式下,当竞争时,sys_futex系统被调用并输入用于处理的内核模式。

  用户模式已经较早说明,本节重点介绍了内核中FUTEX的实现。

  Futex设计了三个基本数据结构:futex_hash_bucket,futex_key,futex_q。

  实际上,还有一个结构__futex_data,如下所示,此

  当futex初始化(futex_init)时,确定障碍物,例如24核CPU,hashsize = 8192。。

  这些数据结构之间的关系如下所示:

  有了我的数据结构,该过程易于理解。Futex_Wait的总体过程如下:

  函数futex_wait_setup主要执行两件事。一种是在uaddr上放哈希,找到futex_hash_bucket并在其上方获取旋转锁。另一个是确定是否期望*uaddr。如果您不相等,您将立即返回,并继续按用户的态度进行尝试。

  然后致电futex_wait_queue_me_me悬挂当前过程:

  futex_wait_queue_me主要做一些事情:

  本文主要从Java中的retentrantlock.lock Process分类。