java源码英文翻译
英文原文 中文译文
Insertion into a CLH queue requires only a single atomic operation on "tail", so there is a simple atomic point of demarcation from unqueued to queued. Similarly, dequeuing involves only updating the "head". However, it takes a bit more work for nodes to determine who their successors are, in part to deal with possible cancellation due to timeouts and interrupts. 向 CLH 队列插入仅需要对“tail”执行一次原子操作,因此存在一个从未入队到入队的简单原子分界点。类似地,出队仅涉及更新“head”。不过,节点要确定自己的后继需要多做一些工作,部分原因是为了处理因超时和中断导致的可能取消情况。
The "prev" links (not used in original CLH locks), are mainly needed to handle cancellation. If a node is cancelled, its successor is (normally) relinked to a non-cancelled predecessor. For explanation of similar mechanics in the case of spin locks, see the papers by Scott and Scherer at http://www.cs.rochester.edu/u/scott/synchronization/ “prev”链接(在原始 CLH 锁中未使用)主要用于处理取消。如果一个节点被取消,它的后继(通常)会重新链接到一个未被取消的前驱。关于自旋锁中类似机制的解释,可参见 Scott 和 Scherer 在 http://www.cs.rochester.edu/u/scott/synchronization/ 上的论文。
We also use "next" links to implement blocking mechanics. The thread id for each node is kept in its own node, so a predecessor signals the next node to wake up by traversing next link to determine which thread it is. Determination of successor must avoid races with newly queued nodes to set the "next" fields of their predecessors. This is solved when necessary by checking backwards from the atomically updated "tail" so that we don't succeed when a node's successor appears to be null. (Or, said differently, the next-links are an optimization so that we don't usually need a backward scan.) 我们还使用“next”链接来实现阻塞机制。每个节点的线程 ID 保存在其自身节点中,因此前驱通过遍历 next 链接来确定是哪个线程,从而向后续节点发信号唤醒。后继的确定必须避免与新入队的节点竞争以设置其前驱的“next”字段。必要时,通过从原子更新的“tail”向后检查来解决这个问题,这样当一个节点的后继看似为空时,我们不会成功(或者说,next 链接是一种优化,因此我们通常不需要向后扫描)。
Cancellation introduces some conservatism to the basic algorithms. Since we must poll for cancellation of other nodes, we can miss noticing whether a cancelled node is still ahead or behind us. This is dealt with by always unparking successors upon cancellation, allowing them to stabilize on a new predecessor, unless we can identify an uncancelled predecessor who will carry this responsibility. 取消给基本算法带来了一些保守性。由于我们必须轮询其他节点的取消情况,可能会遗漏注意到已取消的节点是在我们前面还是后面。这通过在取消时始终解除后继的停放来处理,允许它们稳定在新的前驱上,除非我们能确定一个未被取消的前驱来承担这个责任。
CLH queues need a dummy header node to get started. But we don't create them on construction, because it would be wasted effort if there is never contention. Instead, the node is constructed and head and tail pointers are set upon first contention. CLH 队列需要一个虚拟头节点来启动。但我们不在构造时创建它,因为如果从未有竞争,这会是白费力气。相反,节点在首次竞争时被构造,并且头和尾指针被设置。
Threads waiting on Conditions use the same nodes, but use an additional link. Conditions only need to link nodes in simple (non-concurrent) linked queues because they are only accessed when exclusively held. Upon await, a node is inserted into a condition queue. Upon signal, the node is transferred to the main queue. A special value of status field is used to mark which queue a node is on. 等待条件的线程使用相同的节点,但使用额外的链接。条件仅需要在简单(非并发)的链接队列中链接节点,因为它们仅在独占持有时被访问。在 await 时,一个节点被插入到条件队列中。在 signal 时,该节点被转移到主队列中。status 字段的一个特殊值用于标记节点所在的队列。
Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill Scherer and Michael Scott, along with members of JSR-166 expert group, for helpful ideas, discussions, and critiques on the design of this class. 感谢 Dave Dice、Mark Moir、Victor Luchangco、Bill Scherer 和 Michael Scott,以及 JSR-166 专家组的成员,他们对该类的设计提出了有益的想法、进行了讨论并提出了批评。
static final class Node { 静态最终类 Node {
Marker to indicate a node is waiting in shared mode 标记节点以指示其以共享模式等待
static final Node SHARED = new Node(); 静态最终 Node SHARED = new Node();
Marker to indicate a node is waiting in exclusive mode 标记节点以指示其以独占模式等待
背景与机制解读
• CLH 队列扩展:文中对 CLH 队列在 Java 并发中的适配进行了说明,通过“prev”“next”链接处理线程取消、后继确定等场景,解决了原始 CLH 锁在阻塞同步器中的不足,例如利用“prev”处理取消时的节点重链接,“next”辅助阻塞机制中的线程唤醒与后继确定。
• 条件队列与主队列:等待条件的线程使用的节点通过额外链接管理,在 await 时进入条件队列,signal 时转移到主队列,status 字段标记节点所属队列,实现了条件变量与同步器的协同。
• 设计优化:采用延迟创建虚拟头节点的方式,避免无竞争时的资源浪费;通过对“tail”的原子操作和向后检查等手段,解决节点后继确定时的竞争问题,保障了并发控制的高效性与正确性。