前言
相关系列
- 《Java & Collection & 目录》
- 《Java & Executor & 目录》
- 《Java & Collection/Executor & SynchronousQueue & 源码》
- 《Java & Collection/Executor & SynchronousQueue & 总结》
- 《Java & Collection/Executor & SynchronousQueue & 问题》
涉及内容
- 《Java & Collection & 总结》
- 《Java & Collection & Queue & 总结》
- 《Java & Collection/Executor & BlockingQueue & 总结》
- 《Java & Collection/Executor & LinkedTransferQueue & 总结》
- 《Java & Executor & 总结》
- 《Java & Executor & FutureTask & 总结》
源码
/*
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
/*
*
*
*
*
*
* Written by Doug Lea, Bill Scherer, and Michael Scott with
* assistance from members of JCP JSR-166 Expert Group and released to
* the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package juc;
import juc.locks.LockSupport;
import juc.locks.ReentrantLock;
import sun.misc.Unsafe;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.*;
/**
* A {@linkplain BlockingQueue blocking queue} in which each insert operation must wait for a corresponding remove operation by another
* thread, and vice versa. A synchronous queue does not have any internal capacity, not even a capacity of one. You cannot {@code peek}
* at a synchronous queue because an element is only present when you try to remove it; you cannot insert an element (using any method)
* unless another thread is trying to remove it; you cannot iterate as there is nothing to iterate. The <em>head</em> of the queue is the
* element that the first queued inserting thread is trying to add to the queue; if there is no such queued thread then no element is available
* for removal and {@code poll()} will return {@code null}. For purposes of other {@code Collection} methods (for example {@code contains}),
* a {@code SynchronousQueue} acts as an empty collection. This queue does not permit {@code null} elements.
* 一个阻塞队列,每个插入操作必须等待另一个线程的相应的移除操作,反之亦然。一个同步队列没有任何内部容量,连一个容量都没有。你
* 不能窥视一个同步队列,因为元素只有当你试图删除它时才会出现;你不能插入一个元素(使用任何方法),除非另一个线程试图删除它;
* 你不能迭代,因为没有什么可迭代的。队列的头是首个执行队列插入操作的线程尝试加入队列的元素;如果没有这样的队列线程那就没有元
* 素可用,移除和poll()方法将返回null。为了其它集方法(比如contains()方法),同步队列担当/充当空集。这个队列不允许null值。
* <p>
* Synchronous queues are similar to rendezvous channels used in CSP and Ada. They are well suited for handoff designs, in which an object
* running in one thread must sync up with an object running in another thread in order to hand it some information, event, or task.
* 同步队列类似于CSP和Ada中使用的集合通道。它们非常适合于切换设计,在这种设计中,在一个线程中运行的对象必须与在另一个线程中
* 运行的对象同步,以便将一些信息、事件或任务传递给它。
* <p>
* This class supports an optional fairness policy for ordering waiting producer and consumer threads. By default, this ordering is not
* guaranteed. However, a queue constructed with fairness set to {@code true} grants threads access in FIFO order.
* 这个类支持可选的公平策略用于顺序等待生产者与消费者线程。默认不保证顺序。另外,通过将公平参数设置为true构建的队列能保证线程
* 按FIFO的顺序访问。
* <p>
* This class and its iterator implement all of the <em>optional</em> methods of the {@link Collection} and {@link Iterator} interfaces.
* 这个类和它的迭代器实现集和迭代器接口所有的可选方法。
* <p>
* This class is a member of the <a href="{@docRoot}/../technotes/guides/collections/index.html"> Java Collections Framework</a>.
* 这个类是Java集框架的成员。
*
* @param <E> the type of elements held in this collection 集持有的元素类型
* @author Doug Lea and Bill Scherer and Michael Scott
* @Description: 同步队列
* @since 1.5
*/
public class SynchronousQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
private static final long serialVersionUID = -3223113410248163686L;
/*
* This class implements extensions of the dual stack and dual queue algorithms described in "Nonblocking Concurrent Objects with Condition
* Synchronization", by W. N. Scherer III and M. L. Scott. 18th Annual Conf. on Distributed Computing, Oct. 2004 (see also
* http://www.cs.rochester.edu/u/scott/synchronization/pseudocode/duals.html). The (Lifo) stack is used for non-fair mode, and the (Fifo)
* queue for fair mode. The performance of the two is generally similar. Fifo usually supports higher throughput under contention but Lifo
* maintains higher thread locality in common applications.
* 这个类实现延伸自被描述为"带条件同步的非阻塞并发对象"的双重栈和双重队列算法...。这个(后入先出)栈使用非公平模式,以及这个(先入
* 先出)队列使用公平模式。两者的性能大体相似。先入先出通常在争用下支持更高的吞吐量,但在普通应用程序中,后入先出维护更高的线程
* 局部性(不太理解什么是线程局部性)。
* A dual queue (and similarly stack) is one that at any given time either holds "data" -- items provided by put operations, or "requests" -- slots
* representing take operations, or is empty. A call to "fulfill" (i.e., a call requesting an item from a queue holding data or vice versa) dequeues a
* complementary node. The most interesting feature of these queues is that any operation can figure out which mode the queue is in, and act
* accordingly without needing locks.
* 双队列(和类似的堆栈)是一个在任何指定时间持有"数据"(由放置操作提供的项(即插入/放置))或"请求"(表示拿取操作的槽(即移除/拿
* 取))的队列,或者为空。 一次调用"实现"(即,从持有数据的队列中请求项的调用,反之亦然)出队一个互补节点。这些队列最大有趣特征是
* 任意操作都可以计算出队列处于哪个模式,并在不需要锁的情况下相应的行动。
* Both the queue and stack extend abstract class Transferer defining the single method transfer that does a put or a take. These are unified into
* a single method because in dual data structures, the put and take operations are symmetrical, so nearly all code can be combined. The resulting
* transfer methods are on the long side, but are easier to follow than they would be if broken up into nearly-duplicated parts.
* 队列与栈都继承了抽象类Transferer定义的单例方法transfer()用于实现放置或拿取(方法)。这些统一到一个单例方法中是因为在双数据结构中,
* 放置和拿取操作是对称的,所以几乎所有的代码都能够兼并。由此产生的传输方法比较长,但是比破碎成几乎相同的部分更容易遵循。
* The queue and stack data structures share many conceptual similarities but very few concrete details. For simplicity, they are kept distinct so
* that they can later evolve separately.
* 队列和栈数据结构共同拥有许多概念上的相似之处但在确定的细节上却很少。为了简单起见,它们保持不同,以便以后可以单独发展(即虽然队
* 列和栈的数据结构很相似,但在细节上却很不一样。为了更好的拓展性,两个的数据结构是分割的,确保后期的迭代可以互不影响)。
* The algorithms here differ from the versions in the above paper in extending them for use in synchronous queues, as well as dealing with cancellation.
* The main differences include:
* 这里的算法与上面论文中的版本不同,它们扩展了同步队列的使用,以及处理取消。主要区别包括:
* 1. The original algorithms used bit-marked pointers, but the ones here use mode bits in nodes, leading to a number of further adaptations.
* 1. 最初的算法使用位标记的指针,但这里的算法在节点中使用模式位(即使用了一个字段来记录模式),导致了许多进一步的适应。
* 2. SynchronousQueues must block threads waiting to become fulfilled.
* 2. 同步队列必须阻塞线程等待其变为实现的(除非节点被取消,否则入队的节点只能在实现后出队)。
* 3. Support for cancellation via timeout and interrupts, including cleaning out cancelled nodes/threads from lists to avoid garbage retention and
* memory depletion.
* 3. 通过超时或中断支持取消,包括清理列表中已取消的节点/线程以避免垃圾保留及内存耗尽。
* Blocking is mainly accomplished using LockSupport park/unpark, except that nodes that appear to be the next ones to become fulfilled first spin
* a bit (on multiprocessors only). On very busy synchronous queues, spinning can dramatically improve throughput. And on less busy ones, the
* amount of spinning is small enough not to be noticeable.
* 阻塞主要是使用LockSupport park/unpark完成的,除了看起来是下一个要完成的节点先旋转一点(仅在多处理器上)。(应该是说下个成为已完
* 成的节点会自旋)。在非常忙碌的同步队列上,自旋可以显著地提高吞吐量。并且在不忙的同步队列上,自旋的数量很小也不值得注意。
* Cleaning is done in different ways in queues vs stacks. For queues, we can almost always remove a node immediately in O(1) time (modulo retries
* for consistency checks) when it is cancelled. But if it may be pinned as the current tail, it must wait until some subsequent cancellation. For stacks,
* we need a potentially O(n) traversal to be sure that we can remove the node, but this can run concurrently with other threads accessing the stack.
* 在队列与栈的对比中以不同的方式清理结束(队列与栈以不同的方式进行清理)。对于队列,当节点取消时,我们几乎可以始终在O(1)(时间复杂度)
* 时间(以一致性检查重试为模)内移除一个节点。但是如果它可能被牵制作为当前的尾节点,它必须等待直到随后有节点取消(在队列实现中,如
* 果需要清除的是一个尾节点,则必须将之记录已被后期清理。这是因为线程并发可能会导致清理过程中丢失节点)。对于栈,我们可能需要一次O(n)
* (时间复杂度)遍历来确定我们可以移除节点(在堆栈实现中,要清理的目标节点可能会被其它线程清理(即使我们使用其后继节点做为标记也是一
* 样的),导致我们可能需要从栈顶到栈低进行一次完成的清除),但是这会与其它线程并发地访问这个栈(并发清除/访问)。
* While garbage collection takes care of most node reclamation issues that otherwise complicate nonblocking algorithms, care is taken to "forget"
* references to data, other nodes, and threads that might be held on to long-term by blocked threads. In cases where setting to null would otherwise
* conflict with main algorithms, this is done by changing a node's link to now point to the node itself. This doesn't arise much for Stack nodes (because
* blocked threads do not hang on to old head pointers), but references in Queue nodes must be aggressively forgotten to avoid reachability of
* everything any node has ever referred to since arrival.
* 垃圾收集处理了大多数节点回收问题,否则会使非阻塞算法复杂化,但要注意“忘记”对数据、其他节点和线程的引用,这些数据、节点和线程可能会
* 被阻塞的线程长期占用(数据/对象被阻塞的线程长期持有的话,会很容易因为撑过15次的新生代GC而进入老年代。老年代的对象因为存活时间长,
* 因此GC慢而频率低,对象难以被及时回收。因此线程被唤醒后要及时的断开这些数据/对象的引用,尽可能的促使及时回收,否则的话,可能会延迟
* 的更久)。在设置为null会与主要算法冲突的情况下,可以通过将节点的链接更改为现在指向节点本身来实现(断开链接不能通过将之置null来实现,
* 因为null已经被用来作为各种情况下的标志位,因此使用其引用赋值为自身来实现)。对于堆栈节点来说,这种情况并不多(因为阻塞的线程不会挂在
* 旧的头指针上),但是必须积极地忘记队列节点中的引用,以避免任何节点自到达以来所引用的所有内容都可达。
*/
/**
* Shared internal API for dual stacks and queues.
* 双重栈与队列的共享内部API
*
* @Description: 迁移者类
*/
abstract static class Transferer<E> {
/**
* Performs a put or take.
* 执行放置或拿取。
*
* @param e if non-null, the item to be handed to a consumer; if null, requests that transfer return an item offered by producer.
* 如果不为null,该项用于被一个消费者处理;如果null,请求迁移返回通过一个生产者提供的项。
* @param timed if this operation should timeout 如果该操作需要超时
* @param nanos the timeout, in nanoseconds 超时的纳秒时间
* @return if non-null, the item provided or received; if null, the operation failed due to timeout or interrupt -- the caller can distinguish which of these
* occurred by checking Thread.interrupted.
* 如果不为null,该项提供或接受;如果为null,该操作由于超市或中断而失败 —— 调用者可以通过检查线程是否中断区分发生了哪一个。
* @Description: 迁移:用于向/从队列中插入/移除元素。
*/
abstract E transfer(E e, boolean timed, long nanos);
}
/**
* The number of CPUs, for spin control
* CPU的数量,用于自旋控制
*
* @Description: CPU数:记录CPU的核数,用于管理自旋。
*/
static final int NCPUS = Runtime.getRuntime().availableProcessors();
/**
* The number of times to spin before blocking in timed waits. The value is empirically derived -- it works well across a variety of processors and OSes.
* Empirically, the best value seems not to vary with number of CPUs (beyond 2) so is just a constant.
* (线程)在定时等待之前自旋的次数。该值的获取以经验为主 —— 它很好的访问各式各样的后继者和操作系统。从经验上来说,最好的值似乎不随
* CPU核数(大于等于2)而变化,所以只是一个常量。
*
* @Description: 最大定时自旋:设置定时挂起前自旋的次数。当CPU核数大于1是位32,否则为0(即不自旋,自旋操作只在多核处理器下才有意义)。
*/
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;
/**
* The number of times to spin before blocking in untimed waits. This is greater than timed value because untimed waits spin faster since they don't need
* to check times on each spin.
* (线程)在不定时等待前自旋的次数。该值大于定时值是因为不定时等待自旋更快因为它们不需要在每次自旋中检查时间。
*
* @Description: 最大不定时自旋:设置不定时挂起前自旋的次数。
*/
static final int maxUntimedSpins = maxTimedSpins * 16;
/**
* The number of nanoseconds for which it is faster to spin rather than to use timed park. A rough estimate suffices.
* 纳秒的数量用于更快的自旋而不是使用定时挂起(即自旋的条件,只有少于该时间才会自旋,否则只会挂起)。一个粗略的估计足够。
*
* @Description: 超时自旋阈值:自旋超时的阈值,只有在剩余时间小于等于该值时才会自旋,否则只会挂起。
*/
static final long spinForTimeoutThreshold = 1000L;
/**
* Dual stack
* 双重栈
*
* @Description: 双重栈类
*/
static final class TransferStack<E> extends Transferer<E> {
/*
* This extends Scherer-Scott dual stack algorithm, differing, among other ways, by using "covering" nodes rather than bit-marked pointers:
* Fulfilling operations push on marker nodes (with FULFILLING bit set in mode) to reserve a spot to match a waiting node.
* 拓展自Scherer-Scott(谢勒·斯科特)的双重栈算法,不用于周边的其它方法,通过使用"遮盖"节点而不是位标记指针:
* 完成的操作推动标记节点(在模式中设置完成位)至预留的位置去匹配一个等待中的节点。
*/
/* Modes for SNodes, ORed together in node fields */
/* SNodes的模式,ORed一起在节点字段中 */
/**
* Node represents an unfulfilled consumer
* 节点代表一个未实现的消费者
*
* @Description: 请求:节点是未实现的消费者的状态值。
*/
static final int REQUEST = 0;
/**
* Node represents an unfulfilled producer
* 节点代表一个未实现的生产者
*
* @Description: 数据:节点是未实现的生产者的状态值。
*/
static final int DATA = 1;
/**
* Node is fulfilling another unfulfilled DATA or REQUEST
* 节点是实现中其它未实现的生产者或消费者
*
* @Description: 实现中:节点是未实现的生产者/消费者的状态值。
*/
static final int FULFILLING = 2;
/**
* Returns true if m has fulfilling bit set.
* 如果m存在实现中的位设置则返回true。
*
* @Description: 是否完成中:如果处于实现中则返回true,否则返回false。
*/
static boolean isFulfilling(int m) {
// 判断是否是实现中的状态,该操作是将模式与2进行位与操作实现的。2的二进制表示为10,因此可知当传入的参数(模式)的第二位为1时,
// 其所属的节点就是一个实现者。
return (m & FULFILLING) != 0;
}
/**
* Node class for TransferStacks.
* 迁移栈的节点类
*
* @Description: S节点类
*/
static final class SNode {
/**
* @Description: 下个:持有后继节点的引用。
*/
volatile SNode next; // next node in stack 栈中下个节点
/**
* @Description: 匹配:持有下个相同模式节点的引用。
*/
volatile SNode match; // the node matched to this 节点匹配当前实例
/**
* 等待者:
*/
volatile Thread waiter; // to control park/unpark 控制挂起和接触挂起
/**
* 项:保存的具体数据。
*/
Object item; // data; or null for REQUESTs
/**
* 模式:当前节点的状态。
*/
int mode;
// Note: item and mode fields don't need to be volatile since they are always written before, and read after, other volatile/atomic operations.
// 注意:项和模式字段不需要标注volatile关键字,因为他们始终在写入前以及读取,其它volatile/原子操作后(赋值)。
SNode(Object item) {
this.item = item;
}
/**
* @Description: CAS下个:使用CAS机制,将指定的节点设置为当前节点的后继节点。
*/
boolean casNext(SNode cmp, SNode val) {
return cmp == next && UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
/**
* Tries to match node s to this node, if so, waking up thread. Fulfillers call tryMatch to identify their waiters. Waiters block until they have
* been matched.
* 尝试将S与当前节点(的匹配)相匹配,如果是这样的话,唤醒线程。实现者调用tryMatch()方法识别它们的等待者。等待者阻塞直至他们
* 已经匹配。
*
* @param s the node to match 匹配的节点
* @return true if successfully matched to s 如果s匹配成功则返回true。
* @Description: 尝试匹配:尝试将指定节点与当前节点匹配,如果相同则唤醒等待线程并返回true,否则返回false。如果当前节点的匹配为
* null,则将之赋值为指定节点。
*/
boolean tryMatch(SNode s) {
if (match == null && UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
Thread w = waiter;
if (w != null) {
// waiters need at most one unpark
// 等待者需要至多一次唤醒。
waiter = null;
LockSupport.unpark(w);
}
return true;
}
return match == s;
}
/**
* Tries to cancel a wait by matching node to itself.
* 尝试通过匹配节点至它自己来取消一个等待。
*
* @Description: 尝试取消:将当前节点的匹配设置为自身来表示取消。
*/
void tryCancel() {
UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
}
/**
* @Description: 是否取消:判断当前节点是否已取消,是则返回true;否则返回false。
*/
boolean isCancelled() {
// 通过判断匹配是否与当前节点相同来判断。
return match == this;
}
// Unsafe mechanics
private static final Unsafe UNSAFE;
private static final long matchOffset;
private static final long nextOffset;
static {
try {
UNSAFE = Unsafe.getUnsafe();
Class<?> k = SNode.class;
matchOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("match"));
nextOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
/**
* The head (top) of the stack
* 栈的头
*
* @Description: 头:持有栈头节点的引用。
*/
volatile SNode head;
/**
* @Description: CAS头:通过CAS机制将指定的节点设置为栈的头节点。
*/
boolean casHead(SNode h, SNode nh) {
return h == head && UNSAFE.compareAndSwapObject(this, headOffset, h, nh);
}
/**
* Creates or resets fields of a node. Called only from transfer where the node to push on stack is lazily created and reused when possible to help
* reduce intervals between reads and CASes of head and to avoid surges of garbage when CASes to push nodes fail due to contention.
* 创建或重置节点的字段。(该方法)只从节点的transfer()方法中调用用于当可能帮助减少读取与头的CAS的间隔时以及当由于竞争导致CAS推送
* 节点失败时避免垃圾激增时推动栈懒惰的创建与重生。
*
* @Description: 节点:获取一个指定模式及后继节点的节点。
*/
static SNode snode(SNode s, Object e, SNode next, int mode) {
if (s == null) s = new SNode(e);
s.mode = mode;
s.next = next;
return s;
}
/**
* Puts or takes an item.
* 放置或拿取一个项。
*
* @Description: 迁移:拿取或放置一个元素。
*/
@SuppressWarnings("unchecked")
E transfer(E e, boolean timed, long nanos)