0
点赞
收藏
分享

微信扫一扫

(笔者推荐)【Java权威指南】「官方文档-中英互译」AQS的源码注释分析,贯穿总体核心流程!

(笔者推荐)【Java权威指南】「官方文档-中英互译」AQS的源码注释分析,贯穿总体核心流程!_出队

前提说明

本文主要针对于Java官方文档中的先关的官方注释进行中英文互译,保证了源码坐着的设计思路以及相关知识技能介绍分析等,本文主要进行介绍AQS的源码官方注释的含义介绍,对Java技术有一定领悟的达人会有帮助!

Java官方文档-介绍相关什么是CLH队列(AQS的实现基础)
CLH介绍定义

CLH是基于单向链表的高性能,公平的自旋锁,申请枷锁和线程只需要在本地变量上自旋,来减少CPU缓存和主内存的同步次数,从而降低了总线的内存的开销。

CLH的概念(英文版)

The wait queue is a variant of a "CLH" (Craig,Landin and Hagersten) lock queue. CLH locks are normally used for spinlocks. We instead use them for blocking synchronizers by including explicit("prev" and "next") links plus a "status" field that allow nodes to signal successors when releasing locks, and handle cancellation due to interrupts and timeouts. The status field includes bits that track whether a thread needs a signal (using LockSupport.unpark),Despite these additions,we maintain most CLH locality properties.

CLH的概念(中文版)

这个等待队列是一个CLH锁队列的变体,CLH通常情况下被用来做自旋锁,“我们”改为使用它们来作为实现阻塞同步器,包含线性的(前后)链表,加上一个可以允许在释放锁时候狗后直接点发送一个信号的状态字段,处理相关因为中断超时引起的取消操作,这个状态字段包含位(代码实现时使recheck启用信号和重新检查)可以跟踪一个线程是否需要一个信号,除了这些附加的内容,我们还维护了大多数CLH的本地属性。

CLH的入队操作(英文版)

To enqueue into a CLH lock,you atomically splice it in as new tail. To dequeue, you set the head field,so the next eligible waiter becomes first

CLH的入队操作(中文版)
CLH的入队操作分析(英文版)

Insertion into a CLH queue requires only a single atomic operation on"tail", so there is a simple point of demarcation from un queued to queued.The"next"link of the predecessor is set by the enqueuing thread after successful CAS.Eventhough non-atomic, this suffices to ensure that any blocked thread is signalled by a predecessor when eligible(although in the case of cancellation, possibly with the assistance of a signal in method clean Queue) .Signalling is based in part on a Dekker-like scheme in which the to-be waiting thread indicates WAITING status, then retries acquiring, and then re checks status before blocking.The signaller atomically clears WAITING status when un parking.

CLH的入队操作分析(中文版)

插入CLH队列需要对队尾节点进行原子操作, 所以有一个简单的入队和出队界定指针,当一个线程CAS成功之后会将自己入队, 设置前一个节点的下一个链接(next) 设置成自己(就是如果一个线程入队成功了, 将前一个节点的next设置成自己)。即使是非原子的这个可以确保任何阻塞的线程在符合条件时(即使在取消的情况下,也可以借助clean Queue方法中的一个信号) 会收到来自前一个节点(node) 的信号, 信号发送部分基于类似De kke的方案, 将要等待的线程表示等待状态,然后重新获取,接下来就是在加锁前重新检测状态。当取消等待(un parking) 的时候,信号发送者会自动清理等待状态。

CLH的出队操作分析(英文版)

Dequeuing on acquire involves detaching(nulling) anode's "prev"node and then updating the"head".Other threads check if anode is or was dequeued by checking"prev"rather than head.We enforce the nulling then setting order by spin-waiting if necessary.Because of this, the lock algorithm is not itself strictly"lock-free"because an acquiring thread may need to wait for a previous acquire to make progress.When used with exclusive locks, such progress is required anyway.However Shared mode may(uncommonly) require asp in-wait before setting head field to ensure proper propagation.(Historical note:This allows some simplifications and efficiencies compared to previous versions of this class.)

CLH的出队操作分析(中文版)

当获取时出队列涉及到了分离(置空)一个节点“prev"节点, 然后更新"head"节点。(其实就是出队列的时候当前节点的prev置空或者分离出去) 。其他的线程需要通过检测"prev"而不是“head"来检测一个节点是否(将要/已经)出队列了(这个地方需要通过检测节点的prev判断是否已经出队列了, 判断head不行, 因为head可能已经出队列了)。如果有必要,我们会强制置空,然后通过自旋等待来设置顺序。因为这个原因,锁算法本身不是严格无锁的,因为一个获取锁的线程可能需要等到上一个获取锁的线程运行。当与排他锁一起使用的时候,像这样的运行无论如何都是必须的。然而在共享模式下可能很罕见)需要一个自旋等待,等待确保前一个进程设置了head字段。

CLH的取消节点任务(英文版)

Anode's predecessor can change due to cancellation while it is waiting, until the node is first in queue, at which point it can not change.The acquire method scope with this by rechecking "prev"before waiting.The prev and next fields are modified only via CAS by cancelled nodes in method clean Queue.The un splice strategy is reminiscent of Michael-Scott queues in that after a successful CASto prev field, other threads help fix next fields.Because cancellation often occurs in bunches that complicate decisions about necessary signals, each call to clean Queue traverses the queue until a cleansweep.Nodes that become relinked as first are unconditionally un parked (sometimes unnecessarily, but those cases are not worth avoiding) .

CLH的取消节点任务(中文版)

一个节点的前一个等待节点,在等待的同时可以被取消操作改变,除非当前节点是队列里面的第一个节点,第一个节点不用变。获取方法在等待之前通过再次检测prev来应对处理(是否要改变上一个节点)。在清空方法队列中的被取消的节点,只能通过CAS操作改变上一个和下一个属性。这个不拼接的策略让人想起Michael-Scott队列, 在这个队列里当对prev字段成功CAS之后,其他的线程帮助修改下一个字段。因为取消操作经常是一大堆一大堆的发生,从而关于必要的信号决策变得很难,每次调用都会对队列遍历,直到清除干净。成为重新链接的节点首先会被无条件的出队列(有时虽然不是必须的,但是这些情况都不值得避免。)

CLH的节点抢占锁(英文版)

A thread may try to acquire if it is first(front most) in the *queue, and sometimes before.Being first does not guarantee *success; it only gives the right to contend.We balance *throughput, overhead, and fairness by allowing incoming threads *to"barge"and acquire the synchronizer while in the process of *enqueuing, in which case an awakened first thread may need to *re ​​wait.To​​ counteract possible repeated unlucky re waits, we *exponentially increase retries(up to 256) to acquire each time *a thread i sun parked.Except in this case, AQS locks do not *spin; they instead interleave attempts to acquire with *bookkeeping steps.(Users who want spinlocks can use *try Acquire.)

CLH的节点抢占锁(中文版)

一个线程如果是队列里面的第一个线程可能会尝试获取锁,第一个线程不能确保成功,在它之前的线程也会去尝试获取锁.它仅仅赋予了竞争的权利。在入队列的过程中,通过允许进来的线程来抢占和获取同步器来平衡吞吐量、开销和公平与否,在这种情况下,一个已经被唤醒的第一个线程可能还会需要重新等待。为了抵消可能出现的反复的不幸的重复等待,我们会在每一次线程出队时,指数的增加重试次数来获取锁。除了这种情况, AQS不自旋。他们反而会交错尝试通过记账步骤来获取锁。(想要自旋锁的用户可以使用try Acquire) .

CLH的GC回收和资源释放(英文版)

To improve garbage collectibility, fields of nodes not yet on list are null.(It is not rare to create and then throwaway a node without using it.) Fields of nodes coming off the list are nulled out as soon as possible.This accentuates the challenge of externally determining the first waiting thread(as in method get First Queued Thread) .This sometimes requires the fallback of traversing backwards from the atomically updated "tail"when fields appear null.(This is never needed in the process of signalling though.)

CLH的GC回收和资源释放(中文版)

为了提高垃圾回收的能力,节点的属性字段还没列出的都先为null(节点创建之后还没有使用就被丢掉是很常见的) 节点的属性字段从list上脱落(不适用的时候) 要尽快置为null.这加剧了从外部确定第一个等待线程的挑战。有时当字段为空的时候,需要从原子更新尾部向后遍历来获得反馈(在处理信号的过程中就从来不需要这个操作).

CLH的GC资源创建及共享和互斥模式(英文版)

CLH queues need a dummy header node to get started.But wed on't create the mon construction, because it would be wasted effort if there is never contention.Instead, the node is constructed and head and tail pointers are setup on first contention. Shared mode operations differ from Exclusive in that an acquire signals the next waiter to try to acquire if it is also Shared.The try Acquire Shared APl allows users to indicate the degree of propagation, but in most applications, it is more efficient to ignore this, allowing the successor to try acquiring in any case.

CLH的GC资源创建及共享和互斥模式(中文版)

CLH队列不需要从一个假的头节点来开始, 但是我们不在结构上创建,因为如果永远都没有竞争,那么就是白费力气。相反,节点的构造和头尾指针会在第一次竞争时设置好。共享模式操作和排他的操作的区别在于,如果一个获取锁还是共享的话,它会给下一个等待的线程发送信号让它来尝试获取锁。尝试获取共享锁API可以允许用户表明传播程度, 但是在大多数应用程序中允许后者在任何情况下获取锁,会使效率更高。

CLH的资源抢占和授权控制(英文版)

Threads waiting on Conditions use nodes with an additional link to maintain the(FIFO) list of conditions.Conditions only *need to link nodes in simple(non-concurrent) linked queues *because they are only accessed when exclusively held.Upon *await, anode is inserted into a condition queue.Upon signal,*the node is enqueued on the main queue.A special status field *value is used to track and atomically trigger this. Accesses to fields head, tail, and state usefull Volatile mode, along with CAS.Node fields status, prev and next also do *so while threads maybe sign all able, but sometimes use weaker modes otherwise.Accesses to field"waiter"(the thread to be signalled) are always sandwiched between other atomic accesses so are used in Plain mode.We use jdk.internal Unsafe versions *of atomic access methods rather than Var Handles to avoid *potential VM bootstrap issues.

CLH的资源抢占和授权控制(中文版)

等待条件的线程使用节点时,需要一个也额外的链表来维护条件链表。条件仅需要使用一个简单的链表队列来链接节点,因为它仅仅实在获取到了排他锁之后才允许访问。(不需要线程安全的,因为是获取到了锁之后才能操作,本身安全) 当调用await方法时, 一个节点被插入到条件队列里,当调用信号方法时,节点会从主要队列中出队列。一个特别的状态字段值被用来跟踪和原子的触发这个条件。(什么条件呢?un park 和park中的那个permits),访问属性字段的头、尾以及状态需要使用完全的Volatile模式一同CAS操作, 当线程可以发信号,节点的状态字段、前后指针也需要Volatile和CAS, 但是有时我们使用较弱的模式。尤其是其他原子访问也是使用普通模式的时候,访问等待线程的字段通常是模棱两可的。我们使用Unsafe 原子访问方法的版本而不使用Var Handles, 为了避免VM启动时出现的潜在的风险。

总结归纳(英文)

Most of the above is performed by primary internal method acquire, that is invoked in some way by all exported acquire methods.(It is usually easy for compilers to optimize call-site specializations when heavily used.) There are several arbitrary decisions about when and howto check interrupts in both acquire and await before and/or after blocking.The decisions are less arbitrary in implementation updates because some users appear to relyon original behaviors in ways that are racy and so(rarely) wrong in general but hard to justify changing. Thanks goto Dave Dice, Mark Moir, Victor Luc hang co, Bill Scherer and Michael Scott, along with members of JSR-166expert group, for helpful ideas, discussions, and critiques on the design of this class.

总结归纳(中文)

上面这些大部分都是通过主要的内部acquire方法来运行,在某种轻度上,所有的暴露出来的获取锁方法都会被调用((过度(频)使用通常让编译器对调用者配置优化变得简单)。上面这些大部分都是通过主要的内部acquire方法来运行,在某种轻度上,所有的暴露出来的获取锁方法都会被调用((过度(频)使用通常让编译器对调用者配置优化变得简单)。

举报

相关推荐

0 条评论