SynchronousQueue是什么
- 是一个没有数据缓冲的BlockingQueue,容量为0,它不会为队列中元素维护存储空间,它只是多个线程之间数据交换的媒介。
- 生产者线程对其的插入操作put必须等待消费者的移除操作take。
SynchronousQueue的应用场景
- SynchronousQueue非常适合传递性场景做交换工作,生产者的线程和消费者的线程同步传递某些信息、事件或者任务。
- SynchronousQueue的一个使用场景是在线程池里。如果我们不确定来自生产者请求数量,但是这些请求需要很快的处理掉,那么配合SynchronousQueue为每个生产者请求分配一个消费线程是处理效率最高的办法。
- Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。
SynchronousQueue的公平与非公平模式
- 公平模式(TransferQueue):队尾匹配(判断模式),队头出队,先进先出。
- 非公平模式(默认策略:TransferStack):栈顶匹配,栈顶出栈,后进先出。
SynchronousQueue的特点
- SynchronousQueue 最大的特点在于,它的容量为0,没有一个地方来暂存元素,导致每次取数据都要先阻塞,直到有数据被放入;同理,每次放数据的时候也会阻塞,直到有消费者来取。
- SynchronousQueue 的容量不是 1 而是 0,因为 SynchronousQueue 不需要去持有元素,它所做的就是直接传递(direct handoff)。
- 由于每当需要传递的时候,SynchronousQueue 会把元素直接从生产者传给消费者,在此期间并不需要做存储,所以如果运用得当,它的效率是很高的。
- 使用的数据结构是链表。
- 使用CAS+自旋(无锁),自旋了一定次数后调用 LockSupport.park()进行阻塞。
SynchronousQueue的存取操作
- 存取调用同一个方法:transfer()。
- put、offer 为生产者,携带了数据 e,为 Data 模式,设置到 SNode或QNode 属性中。
- take、poll 为消费者,不携帯数据,为 Request 模式,设置到 SNode或QNode属性中。
SynchronousQueue的大概执行逻辑
- 第一个线程Thread0是消费者访问,此时队列为空,则入队(创建Node结点并赋值)。
- 第二个线程Thread1也是消费者访问,与队尾模式相同,继续入队。
- 第三个线程Thread2是生产者,携带了数据e,与队尾模式不同,不进行入队操作。直接将该线程携带的数据e返回给队首的消费者,并唤醒队首线程Thread1(默认非公平策略是栈结构),出队。
SynchronousQueue的执行过程
- 线程访问阻塞队列,先判断队尾节点或者栈顶节点的 Node 与当前入队模式是否相同。
- 相同则构造节点 Node 入队,并阻塞当前线程,元素 e 和线程赋值给 Node 属性。
- 不同则将元素 e(不为 null) 返回给取数据线程,队首或栈顶线程被唤醒,出队。
SynchronousQueue的构造方法源码分析
/**
* 默认无参的构造方法:直接调用有参的构造方法,传入false(非公平模式)
*/
public SynchronousQueue() {
this(false);
}
/**
* 有参构造方法,参数是是否公平模式。
* fair:true公平模式、false非公平模式。
*/
public SynchronousQueue(boolean fair) {
// 将队列结构或者栈结构对象放在transferer对象上
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
SynchronousQueue的入队方法:put(E e) 源码分析
/**
* SynchronousQueue的入队方法
*/
public void put(E e) throws InterruptedException {
// 如果元素是NULL,抛出异常
if (e == null) throw new NullPointerException();
// 调用公平或者非公平模式的transfer方法
if (transferer.transfer(e, false, 0) == null) {
// 线程中断
Thread.interrupted();
// 抛出中断异常
throw new InterruptedException();
}
}
/**
* SynchronousQueue的出方法
*/
public E take() throws InterruptedException {
// 调用公平或者非公平模式的transfer方法
E e = transferer.transfer(null, false, 0);
// 元素不为null,返回元素
if (e != null)
return e;
// 元素为null,中断线程
Thread.interrupted();
// 抛出中断异常
throw new InterruptedException();
}
SynchronousQueue的公平模式源码分析
// 下一个节点
volatile QNode next; // next node in queue
// 当前元素
volatile Object item; // CAS'ed to or from null
// 当前所属的线程
volatile Thread waiter; // to control park/unpark
// put(true)还是take(false)的标记
final boolean isData;
/**
* 公平模式节点的构造方法
*/
TransferQueue() {
// 构建一个当前的元素为空,标记为take的节点
QNode h = new QNode(null, false); // initialize to dummy node.
// 将头节点设置为当前节点
head = h;
// 将尾节点设置为当前节点
tail = h;
}
/**
* 公平模式的存取方法
*/
E transfer(E e, boolean timed, long nanos) {
// 定义一个可能被使用的当前节点
QNode s = null; // constructed/reused as needed
// 判断当前的元素是不是空:区分是put(true)还是take(flse)
boolean isData = (e != null);
for (;;) {
// 得到队列的尾结点
QNode t = tail;
// 得到队列的头结点
QNode h = head;
// 头尾为空,说明没有被初始化。去下次循环
if (t == null || h == null) // saw uninitialized value
// 继续下次循环
continue; // spin
// 头尾节点相同(只有一个节点,或者为空的时候)或者模式相同
if (h == t || t.isData == isData) { // empty or same-mode
// 得到当前节点的下一个节点
QNode tn = t.next;
// 尾结点不一致,直接继续循环(由于其他节点入队了,导致读不一致)。
if (t != tail) // inconsistent read
// 继续下次循环
continue;
// 当前节点的下个节点有数据:有其他节点入队,并且下一个节点是其他的节点
if (tn != null) { // lagging tail
// 将下一个节点通过CAS的方式设置到尾结点上
advanceTail(t, tn);
// 继续下次循环
continue;
}
// 目前来看put和take传入的值timed都是false。
// 这里表示有时间限制的时候,超时了,直接返回null。
if (timed && nanos <= 0) // can't wait
return null;
// 当前的节点不存在,构建一个新节点,用当前传入的元素
if (s == null)
s = new QNode(e, isData);
// 下一个节点是null,并且将新节点通过CAS的方式放入到下一个节点
if (!t.casNext(null, s)) // failed to link in
// 失败的话继续下次循环
continue;
// 将当前节点设置为尾结点
advanceTail(t, s); // swing tail and wait
// 等待完成:自旋或阻塞线程,直到满足s.item != e(传入的数据不是当前数据)
Object x = awaitFulfill(s, e, timed, nanos);
// 节点被取消、中断或超时
if (x == s) { // wait was cancelled
// 清除s节点(当前节点)
clean(t, s);
// 节点被清除,返回null
return null;
}
// 没有被取消、中断或超时
if (!s.isOffList()) { // not already unlinked
// 如果是头部,取消连接
advanceHead(t, s); // unlink if head
// 当前元素不是空的
if (x != null) // and forget fields
// 在当前元素的设置到节点上
s.item = s;
// 当前节点的持有线程变为null
s.waiter = null;
}
// 返回处理后的元素:这里就是反着来,传入null返回数据,传入数据返回null
return (x != null) ? (E)x : e;
// 模式不相同的逻辑
} else { // complementary-mode
// 得到头结点的下一个节点(要处理的节点)
QNode m = h.next; // node to fulfill
// 尾结点不为空,或者头结点的下个节点不为空,或者头结点有其他线程的变化。进行下次循环(其他线程进行了更改)。
if (t != tail || m == null || h != head)
continue; // inconsistent read
// 得到要处理节点的元素
Object x = m.item;
if (isData == (x != null) || // m already fulfilled : 要处理的节点位置有值(相同的模式)
x == m || // m cancelled : 节点被取消、中断或超时
!m.casItem(x, e)) { // lost CAS : cas不能讲当前元素转变(无值的时候变为有值,或者有值的时候变为无值)
// 尝试跳过头结点
advanceHead(h, m); // dequeue and retry
// 继续下次循环
continue;
}
// 匹配成功,将m变为head,虚拟节点
advanceHead(h, m); // successfully fulfilled
// 唤醒在要处理节点上等待的线程
LockSupport.unpark(m.waiter);
// 返回处理后的元素:这里就是反着来,传入null返回数据,传入数据返回null
return (x != null) ? (E)x : e;
}
}
}
/**
* 将新节点通过CAS的方式设置到尾结点上
*/
void advanceTail(QNode t, QNode nt) {
if (tail == t)
UNSAFE.compareAndSwapObject(this, tailOffset, t, nt);
}
/**
* 等待完成:自旋或阻塞线程,直到满足s.item != e(传入的数据不是当前数据)
*/
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
/* Same idea as TransferStack.awaitFulfill */
// 超时时间的计算,有超时时间就计算,没有超时时间就是0
final long deadline = timed ? System.nanoTime() + nanos : 0L;
// 得到当前的线程
Thread w = Thread.currentThread();
// 计算需要自旋的次数
// 头结点的下一个节点是当前节点的时候:根据电脑性能计算自选次数
// 头结点的下一个节点不是当前节点的时候:自选次数为0
int spins = ((head.next == s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
// 如果当前线程被中断
if (w.isInterrupted())
// 尝试取消当前节点
s.tryCancel(e);
// 得到当前节点的具体元素
Object x = s.item;
// 元素不相同,直接返回当前的元素。tryCancel方法会设置为this导致这里不一致
if (x != e)
return x;
// 如果设置了超时时间,判断超时时间
if (timed) {
// 计算剩余时间
nanos = deadline - System.nanoTime();
// 剩余时间小于0
if (nanos <= 0L) {
// 取消当前节点
s.tryCancel(e);
// 继续下次循环
continue;
}
}
// 步数大于0,步数减一:减少可自选的总数
if (spins > 0)
--spins;
// 次数用完了,设置一下s的等待线程为当前线程
else if (s.waiter == null)
s.waiter = w;
// 没有超时设置的阻塞
else if (!timed)
LockSupport.park(this);
// 超时时间大于1000L的时候,使用判断时间的阻塞(时间nanos大于0才会阻塞)
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
/**
* 尝试取消当前的节点
*/
void tryCancel(Object cmp) {
// 通过CAS的方式将传入的变量设置为this
UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
}
/**
* 清除s节点
*/
void clean(QNode pred, QNode s) {
// 设置S的线程为null
s.waiter = null; // forget thread
/*
* At any given time, exactly one node on list cannot be
* deleted -- the last inserted node. To accommodate this,
* if we cannot delete s, we save its predecessor as
* "cleanMe", deleting the previously saved version
* first. At least one of node s or the node previously
* saved can always be deleted, so this always terminates.
*/
// 最后一个节点要保留一个为null的节点
while (pred.next == s) { // Return early if already unlinked
// 得到头结点
QNode h = head;
// 得到头结点的下一个节点
QNode hn = h.next; // Absorb cancelled first node as head
// 下一个节点不为空并且被取消了
if (hn != null && hn.isCancelled()) {
// 头结点变为下一个节点
advanceHead(h, hn);
// 继续下次循环
continue;
}
// 得到尾结点
QNode t = tail; // Ensure consistent read for tail
// 头尾相等,队列空了,直接返回
if (t == h)
return;
// 得到下一个节点的下一个节点
QNode tn = t.next;
// 尾结点不一致,直接继续循环(由于其他节点入队了,导致读不一致)。
if (t != tail)
// 继续下次循环
continue;
// 头结点的下一个节点不是空的时候
if (tn != null) {
// 尾结点变为下一个节点
advanceTail(t, tn);
// 继续下次循环
continue;
}
// 当前节点不是尾结点,尝试赋值到尾结点
if (s != t) { // If not tail, try to unsplice
// 得到当前节点的下一个节点
QNode sn = s.next;
// 当前节点和下一个节点相同或者cas成功将当前的节点设置为下一个(跳过了当前节点,删除成功)
if (sn == s || pred.casNext(s, sn))
// 直接返回
return;
}
// 走到这里,说明需要删除的s节点是队尾节点,需要使用cleanMe
QNode dp = cleanMe;
// 尝试取消上一个已取消的节点的链接
if (dp != null) { // Try unlinking previous cancelled node
// 得到删除节点的下一个节点(要删除的元素)
QNode d = dp.next;
// 得到要删除元素的下一个元素
QNode dn;
if (d == null || // d is gone or :要删除的元素已经删除了
d == dp || // d is off list or : 要删除的元素不在列表上
!d.isCancelled() || // d not cancelled or :要删除的元素没有被取消
(d != t && // d not tail and : 要删除的元素不是尾结点
(dn = d.next) != null && // has successor : 要删除的元素有后继节点
dn != d && // that is on list : 在这个名单上
dp.casNext(d, dn))) // d unspliced : 可以cas到下一步
// 通过CAS的方式把cleanMe变为null
casCleanMe(dp, null);
// 要删除的元素和传入的元素一致:当前节点已保存到节点
if (dp == pred)
return; // s is already saved node
// 当前节点可以放入到cleanMe中,推迟清除,直接返回
} else if (casCleanMe(null, pred))
return; // Postpone cleaning s
}
}
/**
* 判断节点是否被取消:取消的时候会cas节点为this
*/
boolean isCancelled() {
return item == this;
}
/**
* 头结点变为下一个节点
*/
void advanceHead(QNode h, QNode nh) {
if (h == head &&
UNSAFE.compareAndSwapObject(this, headOffset, h, nh))
h.next = h; // forget old next
}
SynchronousQueue的非公平模式源码分析
/**
* 非公平模式的存取方法
*/
E transfer(E e, boolean timed, long nanos) {
// 义一个可能被使用的当前节点
SNode s = null; // constructed/reused as needed
// 得到模式:true为take,false为put
int mode = (e == null) ? REQUEST : DATA;
for (;;) {
//得到投机诶单
SNode h = head;
// 头结点为空或者节点模式与头结点相同,压栈
if (h == null || h.mode == mode) { // empty or same-mode
// 有超时时间的设置,并且超时时间设置小于0
if (timed && nanos <= 0) { // can't wait
// 头结点不为空,并且被取消了
if (h != null && h.isCancelled())
// 弹出头结点。无后续代码,进入下次循环
casHead(h, h.next); // pop cancelled node
else
// 超时了直接返回null
return null;
// 没有超时的情况,尝试将s设置为头结点
} else if (casHead(h, s = snode(s, e, h, mode))) {
// 自选,等待线程匹配成功
SNode m = awaitFulfill(s, timed, nanos);
// 等待的线程被取消了,清除当前节点
if (m == s) { // wait was cancelled
// 清除s节点(当前节点)
clean(s);
// 节点被清除,直接返回null
return null;
}
// 头结点不是空的并且头节点的下一个节点是当前节点
if ((h = head) != null && h.next == s)
// 将s的下一个节点设置为头结点
casHead(h, s.next); // help s's fulfiller
// 返回处理后的元素:这里就是反着来,传入null返回数据,传入数据返回null
return (E) ((mode == REQUEST) ? m.item : s.item);
}
// 节点模式不相同,并且(m & FULFILLING) != 0 。model的值为1或者0(上门的逻辑创建的节点)返回false,model的值2或者3(这个判断里面创建的节点)返回true
} else if (!isFulfilling(h.mode)) { // try to fulfill
// 头结点被取消
if (h.isCancelled()) // already cancelled
// 移除头结点
casHead(h, h.next); // pop and retry
// 创建一个节点,这个节点的状态为2或者3。2 & 2 = 2。3 & 2 = 2。
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
for (;;) { // loop until matched or waiters disappear
// 得到当前节点的下一个节点:新创建的元素中,m是头结点
SNode m = s.next; // m is s's match
// 头结点被其他线程执行了
if (m == null) { // all waiters are gone
// 移除当前节点
casHead(s, null); // pop fulfill node
// 取消节点的引用
s = null; // use new node next time
// 跳出内部的循环
break; // restart main loop
}
// 得到头结点的下一个节点
SNode mn = m.next;
// 尝试匹配:匹配成功会唤醒等待的线程
if (m.tryMatch(s)) {
// s是头结点的时候,移除!
casHead(s, mn); // pop both s and m
// 返回处理后的元素:这里就是反着来,传入null返回数据,传入数据返回null
return (E) ((mode == REQUEST) ? m.item : s.item);
// 唤醒失败了,s不是头结点
} 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
// 栈顶变为null
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
// 匹配失败,m是栈顶,尝试移除当前栈顶
h.casNext(m, mn); // help unlink
}
}
}
}
/**
* 栈结构的构造方法
*/
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;
}
/**
* 等待完成:自旋或阻塞线程,直到匹配成功
*/
SNode awaitFulfill(SNode s, boolean timed, long nanos) {
// 超时时间的计算,有超时时间就计算,没有超时时间就是0
final long deadline = timed ? System.nanoTime() + nanos : 0L;
// 得到当前的线程
Thread w = Thread.currentThread();
// 计算需要自旋的次数
// 头结点的下一个节点是当前节点的时候:根据电脑性能计算自选次数
// 头结点的下一个节点不是当前节点的时候:自选次数为0
int spins = (shouldSpin(s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
// 如果当前线程被中断
if (w.isInterrupted())
// 尝试取消当前节点
s.tryCancel();
// 获取当前节点的匹配节点
SNode m = s.match;
// 节点不为空,直接返回
if (m != null)
return m;
// 如果设置了超时时间,判断超时时间
if (timed) {
// 计算剩余时间
nanos = deadline - System.nanoTime();
// 剩余时间小于0
if (nanos <= 0L) {
// 尝试取消当前节点
s.tryCancel();
// 继续下次循环
continue;
}
}
// 步数大于0,步数减一:减少可自选的总数
if (spins > 0)
spins = shouldSpin(s) ? (spins-1) : 0;
// 次数用完了,设置一下s的等待线程为当前线程
else if (s.waiter == null)
s.waiter = w; // establish waiter so can park next iter
// 没有超时设置的阻塞
else if (!timed)
LockSupport.park(this);
// 超时时间大于1000L的时候,使用判断时间的阻塞(时间nanos大于0才会阻塞)
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
/**
* 尝试取消当前的节点
*/
void tryCancel() {
UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
}
/**
* 清除s节点
*/
void clean(SNode s) {
// 设置当前的元素为null
s.item = null; // forget item
// 设置S的线程为null
s.waiter = null; // forget thread
// 得到当前节点的下一个节点
SNode past = s.next;
// 下一个个节点有值但是被取消了,尝试获取下下个
if (past != null && past.isCancelled())
past = past.next;
// Absorb cancelled nodes at head
// 定义一个临时的节点
SNode p;
// 头结点不为空并且头结点不是下一个节点并且头结点是被取消
while ((p = head) != null && p != past && p.isCancelled())
// 通过CAS的方式将头结点变为下一个节点,直到找到一个没有被取消的
casHead(p, p.next);
// Unsplice embedded nodes
// 头结点不为空,并且和下一个节点不相等(头结点为null)
while (p != null && p != past) {
// 得到下一个节点
SNode n = p.next;
// 下一个节点不为空,并且没有被取消
if (n != null && n.isCancelled())
// 尝试头结点变为下一个:清除掉现在的头结点
p.casNext(n, n.next);
else
// 被取消了的话,直接跳过头结点!
p = n;
}
}
/**
* 尝试匹配:匹配成功会唤醒等待的线程
*/
boolean tryMatch(SNode s) {
// 匹配节点为空并且CAS成功变为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;
}
结束语
- 获取更多本文的前置知识文章,以及新的有价值的文章,让我们一起成为架构师!
- 关注公众号,可以让你对MySQL有非常深入的了解
- 关注公众号,每天持续高效的了解并发编程!
- 关注公众号,后续持续高效的了解spring源码!
- 这个公众号,无广告!!!每日更新!!!