一、SynchronousQueue API介绍
public class SynchronousQueue<E>extends AbstractQueue<E>implements BlockingQueue<E>, Serializable
一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行 peek,因为仅在试图要取得元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)添加元素;也不能迭代队列,因为其中没有元素可用于迭代。队列的头 是尝试添加到队列中的首个已排队线程元素;如果没有已排队线程,则不添加元素并且头为 null。对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空集合。此队列不允许 null 元素。
同步队列类似于 CSP 和 Ada 中使用的 rendezvous 信道。它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。
对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略。默认情况下不保证这种排序。但是,使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。公平通常会降低吞吐量,但是可以减小可变性并避免得不到服务。
此类及其迭代器实现 Collection 和 Iterator 接口的所有可选 方法。
此类是 Java Collections Framework 的成员。
同步队列类似于 CSP 和 Ada 中使用的 rendezvous 信道。它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。
对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略。默认情况下不保证这种排序。但是,使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。公平通常会降低吞吐量,但是可以减小可变性并避免得不到服务。
此类及其迭代器实现 Collection 和 Iterator 接口的所有可选 方法。
此类是 Java Collections Framework 的成员。
二、核心方法
1、put
public void put(E o) throws InterruptedException {
if (o == null) throw new NullPointerException();
if (transferer.transfer(o, false, 0) == null) {
Thread.interrupted();
throw new InterruptedException();
}
}
2、offer
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
return transferer.transfer(e, true, 0) != null;
}
3、take
public E take() throws InterruptedException {
Object e = transferer.transfer(null, false, 0);
if (e != null)
return (E)e;
Thread.interrupted();
throw new InterruptedException();
}
4、poll
public E poll() {
return (E)transferer.transfer(null, true, 0);
}
三、内部实现类TransferStack
1、数据结构SNode
该结构是一个链表结构,并且里面的属性next,match以及waiter都是volatile类型的
该结构提供了tryMatch的方法,将match指向给定的SNode节点,并且如果给定的节点有等待的线程,则置为null,并且解除等待线程的阻塞状态
该结构提供的tryCancel方法,则是使用cas算法将match指向自身。
/** Node class for TransferStacks. */
static final class SNode {
volatile SNode next; // next node in stack
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.
SNode(Object item) {
this.item = item;
}
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.
*
* @param s the node to match
* @return true if successfully matched to s
*/
//如果match对象为空,则使用cas算法将match指向给定的节点s,
//如果节点s有等待的线程,则置为空,并且解除等待线程的阻塞方法
boolean tryMatch(SNode s) {
if (match == null && //如果match为空,则将当前节点的match 置为节点s
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.
*/
void tryCancel() {
UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
}
boolean isCancelled() {
return match == this;
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long matchOffset;
private static final long nextOffset;
static {
try {
UNSAFE = sun.misc.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);
}
}
}
2、核心方法 transfer
Object transfer(Object e, boolean timed, long nanos) {
/*
* Basic algorithm is to loop trying one of three actions:
*
* 1. If apparently empty or already containing nodes of same
* mode, try to push node on stack and wait for a match,
* returning it, or null if cancelled.
*
* 2. If apparently containing node of complementary mode,
* try to push a fulfilling node on to stack, match
* with corresponding waiting node, pop both from
* stack, and return matched item. The matching or
* unlinking might not actually be necessary because of
* other threads performing action 3:
*
* 3. If top of stack already holds another fulfilling node,
* help it out by doing its match and/or pop
* operations, and then continue. The code for helping
* is essentially the same as for fulfilling, except
* that it doesn't return the item.
*/
SNode s = null; // constructed/reused as needed
int mode = (e == null) ? REQUEST : DATA;//如果e为空则说明是出栈(take,poll)操作,否则是入栈(put,offer)操作,e代表入栈的数据
for (;;) {
SNode h = head;
// 如果栈顶为空或者栈顶的模式和 传递过来的请求模式一样,
//ps:一开始的话 栈顶肯定为空;请求模式一样,可以理解为当前操作与栈顶是同类型的操作,都是入栈或者出栈
if (h == null || h.mode == mode) { // empty or same-mode
if (timed && nanos <= 0) { // can't wait //如果有超时限制,这块暂时不研究
if (h != null && h.isCancelled())
casHead(h, h.next); // pop cancelled node
else
return null;
} else if (casHead(h, s = snode(s, e, h, mode))) { //首先创建新的节点s并且压栈,
//等待线程中断或者阻塞或者节点s的match元素不为空
//如果线程中断,则节点s的match指向自身,
//循环完毕N次之后,设置s节点的等待线程为当前线程,如果没有超时限制,则调用方法
//LockSupport.park(this); 来阻塞当前线程,
//直到 其它操作获取到节点s之后,操作s的match元素
//之后获取到s节点的等待线程,通过 unpark 解除线程的阻塞状态
SNode m = awaitFulfill(s, timed, nanos); //
if (m == s) { // wait was cancelled//如果线程被中断,则返回null
clean(s);
return null;
}
//如果栈顶不为空,并且栈顶指向的next是s,则将栈顶置换为s指向的下个节点
if ((h = head) != null && h.next == s)
casHead(h, s.next); // help s's fulfiller
//如果当前mode是出栈操作,则返回m的item,如果当前mode是入栈操作,则返回s的item
//注意此时的栈的数据结构是 s是新的节点,m 是s的match元素,
//如果是入栈,则说明前一操作是出栈,但是没有数据,所以一直在等待,这时候入栈,正好满足需求
//如过是出栈,则说明前一操作是入栈,所以可以直接获取到入栈的数据
return (mode == REQUEST) ? m.item : s.item;
}
} else if (!isFulfilling(h.mode)) { // try to fulfill 此时可以确定head!=null&&h.mode!=mode
//如果发生了取消,则h的match指向自身,置换栈顶(h)的元素为h的next节点
if (h.isCancelled()) // already cancelled
casHead(h, h.next); // pop and retry
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {//创建新的节点s,并且压栈
for (;;) { // loop until matched or waiters disappear
SNode m = s.next; // m is s's match //获取s的next节点,相当于旧的head
if (m == null) { // all waiters are gone //重复检查如果m 为空,
casHead(s, null); // pop fulfill node //如果此时的栈顶元素为s,那么将栈顶元素置为空
s = null; // use new node next time
break; // restart main loop
}
SNode mn = m.next; //获取m的下一个节点 mn
if (m.tryMatch(s)) { //如果节点m的match设置为s成功,
casHead(s, mn); // pop both s and m //如果栈顶元素为s,将栈顶元素置为mn,
return (mode == REQUEST) ? m.item : s.item;
} else // lost match
s.casNext(m, mn); // help unlink
}
}
} else { // help a fulfiller
SNode m = h.next; // m is h's match
if (m == null) // waiter is gone
casHead(h, null); // pop fulfilling node
else {
SNode mn = m.next;
if (m.tryMatch(h)) // help match
casHead(h, mn); // pop both h and m
else // lost match
h.casNext(m, mn); // help unlink
}
}
}
}
3、核心方法 awaitFulfill
long lastTime = timed ? System.nanoTime() : 0; //是否设置了超时设置
Thread w = Thread.currentThread();
SNode h = head;
int spins = (shouldSpin(s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
if (w.isInterrupted()) //如果当前线程被中断,则调用节点的tryCancel方法,实则是原子操作,如果s的match为null,则将s的match 指向自身
s.tryCancel();
SNode m = s.match;
if (m != null) //如果match 不为空,则返回m
return m;
if (timed) {
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
if (nanos <= 0) {
s.tryCancel();
continue;
}
}
if (spins > 0) //spins大于0 的时候一直递减,直到为0
spins = shouldSpin(s) ? (spins-1) : 0;
else if (s.waiter == null) //此时循环了 spins 次,仍然没有退出循环,即没有返回数据,如果节点s的等待线程为空,则设置为当前线程
s.waiter = w; // establish waiter so can park next iter
else if (!timed) //如果没有设置超时限制,
LockSupport.park(this); //设置当前线程为阻塞状态
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
个人分析得出一个大致的结论
使用SynchronousQueue,在非公平策略下,队列的入队和出队操作会被封装成Snode节点并且压栈,
当前后两个操作不一致,其中一个是入队另一个是出队,这种情况发生的时候,
TransferStack会一次性弹出 栈顶的2个元素,并且设置第一个元素的match 指向第二个元素,并且解除第二个元素等待线程的阻塞状态。然后将栈顶元素置为第二个元素的next节点。
如果第一个元素是入队,那么第二个元素是出队,返回入队的数据;如果第一个元素是出队操作,第二个元素是入队,返回入队的数据,
判断操作是入队还是出队,可以根据传入transfer方法的 Object e 是否为空来判断,如果是入队,则e不为空,否则e为空。
判断连续的两个操作是否一致,可以将栈顶的mode 与当前操作的mode 比较是否一致来判断。
return (mode == REQUEST) ? m.item : s.item; 可以将m理解为第二个元素,s理解为第一个元素。