一、SynchronousQueue的put方法底层源码

SynchronousQueue 的 put 方法用于将元素插入队列。由于 SynchronousQueue 没有实际的存储空间,put 方法会阻塞,直到有消费者线程调用 take 方法移除元素

1、put 方法的作用

  • 将元素插入队列。

  • 如果没有消费者线程等待,当前线程会阻塞,直到有消费者线程移除元素。

  • 该方法不会返回任何值,也不会抛出异常(除非线程被中断)。

2、put 方法的源码

以下是 SynchronousQueue 中 put 方法的源码(基于 JDK 17):

可以看到,put 方法的核心逻辑是通过 transferer.transfer 方法实现的。transferer 是 SynchronousQueue 的内部组件,负责实际的数据传输

3、transferer.transfer 方法

transferer 是一个抽象类,有两个实现:

  • TransferStack:用于非公平模式。

  • TransferQueue:用于公平模式。

以下是 TransferStack 和 TransferQueue 中 transfer 方法的通用逻辑:

(1)TransferStack.transfer 方法

  E transfer(E e, boolean timed, long nanos) {
SNode s = null; // 创建一个新节点
int mode = (e == null) ? REQUEST : DATA; // 判断是生产者还是消费者 for (;;) {
SNode h = head; // 获取栈顶节点
if (h == null || h.mode == mode) { // 如果栈为空或模式匹配
if (timed && nanos <= 0) { // 如果超时
if (h != null && h.isCancelled()) // 如果节点已取消
casHead(h, h.next); // 移除已取消的节点
else
return null; // 返回 null
} else if (casHead(h, s = snode(s, e, h, mode))) { // 尝试插入新节点
SNode m = awaitFulfill(s, timed, nanos); // 等待匹配
if (m == s) { // 如果节点被取消
clean(s); // 清理节点
return null; // 返回 null
}
if ((h = head) != null && h.next == s) // 如果匹配成功
casHead(h, s.next); // 移除匹配的节点
return (E) ((mode == REQUEST) ? m.item : s.item); // 返回数据
}
} else if (!isFulfilling(h.mode)) { // 如果栈顶节点未完成匹配
if (h.isCancelled()) // 如果节点已取消
casHead(h, h.next); // 移除已取消的节点
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) { // 尝试插入新节点
for (;;) {
SNode m = s.next; // 获取下一个节点
if (m == null) { // 如果下一个节点为空
casHead(s, null); // 重置栈顶
s = null; // 重置节点
break;
}
SNode mn = m.next;
if (m.tryMatch(s)) { // 尝试匹配
casHead(s, mn); // 移除匹配的节点
return (E) ((mode == REQUEST) ? m.item : s.item); // 返回数据
} else
s.casNext(m, mn); // 移除未匹配的节点
}
}
} else { // 如果栈顶节点已完成匹配
SNode m = h.next; // 获取下一个节点
if (m == null) // 如果下一个节点为空
casHead(h, null); // 重置栈顶
else {
SNode mn = m.next;
if (m.tryMatch(h)) // 尝试匹配
casHead(h, mn); // 移除匹配的节点
else
h.casNext(m, mn); // 移除未匹配的节点
}
}
}
}

(2)TransferQueue.transfer 方法

  E transfer(E e, boolean timed, long nanos) {
QNode s = null; // 创建一个新节点
boolean isData = (e != null); // 判断是生产者还是消费者 for (;;) {
QNode t = tail;
QNode h = head;
if (t == null || h == null) // 如果队列未初始化
continue; if (h == t || t.isData == isData) { // 如果队列为空或模式匹配
QNode tn = t.next;
if (t != tail) // 如果 tail 已更新
continue;
if (tn != null) { // 如果 tail 未更新
advanceTail(t, tn); // 更新 tail
continue;
}
if (timed && nanos <= 0) // 如果超时
return null; // 返回 null
if (s == null) // 如果节点未初始化
s = new QNode(e, isData); // 创建新节点
if (!t.casNext(null, s)) // 尝试插入新节点
continue; advanceTail(t, s); // 更新 tail
Object x = awaitFulfill(s, e, timed, nanos); // 等待匹配
if (x == s) { // 如果节点被取消
clean(t, s); // 清理节点
return null; // 返回 null
} if (!s.isOffList()) { // 如果节点未移除
advanceHead(t, s); // 更新 head
if (x != null) // 如果匹配成功
s.item = s; // 标记节点
s.waiter = null; // 清除等待线程
}
return (x != null) ? (E)x : e; // 返回数据
} else { // 如果模式不匹配
QNode m = h.next;
if (t != tail || m == null || h != head) // 如果队列已更新
continue; Object x = m.item;
if (isData == (x != null) || x == m || !m.casItem(x, e)) { // 如果匹配失败
advanceHead(h, m); // 移除未匹配的节点
continue;
} advanceHead(h, m); // 更新 head
LockSupport.unpark(m.waiter); // 唤醒等待线程
return (x != null) ? (E)x : e; // 返回数据
}
}
}

4、关键点总结

  • 无存储空间:SynchronousQueue 没有容量,插入和移除操作必须一一对应。

  • 阻塞行为:如果没有配对的插入或移除操作,线程会一直阻塞。

  • 公平性:公平模式下,等待时间最长的线程优先获得执行机会。

二、SynchronousQueue的类结构

先看一下SynchronousQueue类里面有哪些属性:

public class SynchronousQueue<E>
extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable { /**
* 转接器(栈和队列的父类)
*/
abstract static class Transferer<E> { /**
* 转移(put和take都用这一个方法)
*
* @param e 元素
* @param timed 是否超时
* @param nanos 纳秒
*/
abstract E transfer(E e, boolean timed, long nanos); } /**
* 栈实现类
*/
static final class TransferStack<E> extends Transferer<E> {
} /**
* 队列实现类
*/
static final class TransferQueue<E> extends Transferer<E> {
} }

SynchronousQueue底层是基于Transferer抽象类实现的,放数据和取数据的逻辑都耦合在transfer()方法中。而Transferer抽象类又有两个实现类,分别是基于栈结构实现和基于队列实现

1、初始化

SynchronousQueue常用的初始化方法有两个:

  • 1、无参构造方法

  • 2、指定容量大小的有参构造方法

       /**
    * 无参构造方法
    */
    BlockingQueue<Integer> blockingQueue1 = new SynchronousQueue<>(); /**
    * 有参构造方法,指定是否使用公平锁(默认使用非公平锁)
    */
    BlockingQueue<Integer> blockingQueue2 = new SynchronousQueue<>(true);

再看一下对应的源码实现:

  /**
* 无参构造方法
*/
public SynchronousQueue() {
this(false);
} /**
* 有参构造方法,指定是否使用公平锁
*/
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}

可以看出SynchronousQueue的无参构造方法默认使用的非公平策略,有参构造方法可以指定使用公平策略。 操作策略:

  • 1、公平策略,基于队列实现的是公平策略,先进先出。

  • 2、非公平策略,基于栈实现的是非公平策略,先进后出。

2、栈的类结构

/**
* 栈实现
*/
static final class TransferStack<E> extends Transferer<E> { /**
* 头节点(也是栈顶节点)
*/
volatile SNode head; /**
* 栈节点类
*/
static final class SNode { /**
* 当前操作的线程
*/
volatile Thread waiter; /**
* 节点值(取数据的时候,该字段为null)
*/
Object item; /**
* 节点模式(也叫操作类型)
*/
int mode; /**
* 后继节点
*/
volatile SNode next; /**
* 匹配到的节点
*/
volatile SNode match; }
}

节点模式有以下三种:

3、栈的transfer方法实现

transfer()方法中,把放数据和取数据的逻辑耦合在一块了,逻辑有点绕,不过核心逻辑就四点,把握住就能豁然开朗。其实就是从栈顶压入,从栈顶弹出。

详细流程如下:

1、首先判断当前线程的操作类型与栈顶节点的操作类型是否一致,比如都是放数据,或者都是取数据。

2、如果是一致,把当前操作包装成SNode节点,压入栈顶,并挂起当前线程。

3、如果不一致,表示相互匹配(比如当前操作是放数据,而栈顶节点是取数据,或者相反)。然后也把当前操作包装成SNode节点压入栈顶,并使用tryMatch()方法匹配两个节点,匹配成功后,弹出两个这两个节点,并唤醒栈顶节点线程,同时把数据传递给栈顶节点线程,最后返回。

4、栈顶节点线程被唤醒,继续执行,然后返回传递过来的数据。

  /**
* 转移(put和take都用这一个方法)
*
* @param e 元素(取数据的时候,元素为null)
* @param timed 是否超时
* @param nanos 纳秒
*/
E transfer(E e, boolean timed, long nanos) {
SNode s = null;
// 1. e为null,表示要取数据,否则是放数据
int mode = (e == null) ? REQUEST : DATA;
for (; ; ) {
SNode h = head;
// 2. 如果本次操作跟栈顶节点模式相同(都是取数据,或者都是放数据),就把本次操作包装成SNode,压入栈顶
if (h == null || h.mode == mode) {
if (timed && nanos <= 0) {
if (h != null && h.isCancelled()) {
casHead(h, h.next);
} else {
return null;
}
// 3. 把本次操作包装成SNode,压入栈顶,并挂起当前线程
} else if (casHead(h, s = snode(s, e, h, mode))) {
// 4. 挂起当前线程
SNode m = awaitFulfill(s, timed, nanos);
if (m == s) {
clean(s);
return null;
}
// 5. 当前线程被唤醒后,如果栈顶有了新节点,就删除当前节点
if ((h = head) != null && h.next == s) {
casHead(h, s.next);
}
return (E) ((mode == REQUEST) ? m.item : s.item);
}
// 6. 如果栈顶节点类型跟本次操作不同,并且模式不是FULFILLING类型
} else if (!isFulfilling(h.mode)) {
if (h.isCancelled()) {
casHead(h, h.next);
}
// 7. 把本次操作包装成SNode(类型是FULFILLING),压入栈顶
else if (casHead(h, s = snode(s, e, h, FULFILLING | mode))) {
// 8. 使用死循环,直到匹配到对应的节点
for (; ; ) {
// 9. 遍历下个节点
SNode m = s.next;
// 10. 如果节点是null,表示遍历到末尾,设置栈顶节点是null,结束。
if (m == null) {
casHead(s, null);
s = null;
break;
}
SNode mn = m.next;
// 11. 如果栈顶的后继节点跟栈顶节点匹配成功,就删除这两个节点,结束。
if (m.tryMatch(s)) {
casHead(s, mn);
return (E) ((mode == REQUEST) ? m.item : s.item);
} else {
// 12. 如果没有匹配成功,就删除栈顶的后继节点,继续匹配
s.casNext(m, mn);
}
}
}
} else {
// 13. 如果栈顶节点类型跟本次操作不同,并且是FULFILLING类型,
// 就再执行一遍上面第8步for循环中的逻辑(很少概率出现)
SNode m = h.next;
if (m == null) {
casHead(h, null);
} else {
SNode mn = m.next;
if (m.tryMatch(h)) {
casHead(h, mn);
} else {
h.casNext(m, mn);
}
}
}
}
}

不用关心细枝末节,把握住代码核心逻辑即可。 再看一下第4步,挂起线程的代码逻辑: 核心逻辑就两条:

  • 第6步,挂起当前线程

  • 第3步,当前线程被唤醒后,直接返回传递过来的match节点

     /**
    * 等待执行
    *
    * @param s 节点
    * @param timed 是否超时
    * @param nanos 超时时间
    */
    SNode awaitFulfill(SNode s, boolean timed, long nanos) {
    // 1. 计算超时时间
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    Thread w = Thread.currentThread();
    // 2. 计算自旋次数
    int spins = (shouldSpin(s) ?
    (timed ? maxTimedSpins : maxUntimedSpins) : 0);
    for (; ; ) {
    if (w.isInterrupted())
    s.tryCancel();
    // 3. 如果已经匹配到其他节点,直接返回
    SNode m = s.match;
    if (m != null)
    return m;
    if (timed) {
    // 4. 超时时间递减
    nanos = deadline - System.nanoTime();
    if (nanos <= 0L) {
    s.tryCancel();
    continue;
    }
    }
    // 5. 自旋次数减一
    if (spins > 0)
    spins = shouldSpin(s) ? (spins - 1) : 0;
    else if (s.waiter == null)
    s.waiter = w;
    // 6. 开始挂起当前线程
    else if (!timed)
    LockSupport.park(this);
    else if (nanos > spinForTimeoutThreshold)
    LockSupport.parkNanos(this, nanos);
    }
    }

再看一下匹配节点的tryMatch()方法逻辑: 作用就是唤醒栈顶节点,并当前节点传递给栈顶节点。

  /**
* 匹配节点
*
* @param s 当前节点
*/
boolean tryMatch(SNode s) {
if (match == null &&
UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
Thread w = waiter;
if (w != null) {
waiter = null;
// 1. 唤醒栈顶节点
LockSupport.unpark(w);
}
return true;
}
// 2. 把当前节点传递给栈顶节点
return match == s;
}

4、队列的类结构

  /**
* 队列实现
*/
static final class TransferQueue<E> extends Transferer<E> { /**
* 头节点
*/
transient volatile QNode head; /**
* 尾节点
*/
transient volatile QNode tail; /**
* 队列节点类
*/
static final class QNode { /**
* 当前操作的线程
*/
volatile Thread waiter; /**
* 节点值
*/
volatile Object item; /**
* 后继节点
*/
volatile QNode next; /**
* 当前节点是否为数据节点
*/
final boolean isData;
}
}

可以看出TransferQueue队列是使用带有头尾节点的单链表实现的。 还有一点需要提一下,TransferQueue默认构造方法,会初始化头尾节点,默认是空节点。

/**
* TransferQueue默认的构造方法
*/
TransferQueue() {
QNode h = new QNode(null, false);
head = h;
tail = h;
}

队列的transfer方法实现

队列使用的公平策略,体现在,每次操作的时候,都是从队尾压入,从队头弹出。 详细流程如下:

1、首先判断当前线程的操作类型与队尾节点的操作类型是否一致,比如都是放数据,或者都是取数据。

2、如果是一致,把当前操作包装成QNode节点,压入队尾,并挂起当前线程。

3、如果不一致,表示相互匹配(比如当前操作是放数据,而队尾节点是取数据,或者相反)。然后在队头节点开始遍历,找到与当前操作类型相匹配的节点,把当前操作的节点值传递给这个节点,并弹出这个节点,唤醒这个节点的线程,最后返回。

4、队头节点线程被唤醒,继续执行,然后返回传递过来的数据。

    /**
* 转移(put和take都用这一个方法)
*
* @param e 元素(取数据的时候,元素为null)
* @param timed 是否超时
* @param nanos 超时时间
*/
E transfer(E e, boolean timed, long nanos) {
QNode s = null;
// 1. e不为null,表示要放数据,否则是取数据
boolean isData = (e != null);
for (; ; ) {
QNode t = tail;
QNode h = head;
if (t == null || h == null) {
continue;
} // 2. 如果本次操作跟队尾节点模式相同(都是取数据,或者都是放数据),就把本次操作包装成QNode,压入队尾
if (h == t || t.isData == isData) {
QNode tn = t.next;
if (t != tail) {
continue;
}
if (tn != null) {
advanceTail(t, tn);
continue;
}
if (timed && nanos <= 0) {
return null;
}
// 3. 把本次操作包装成QNode,压入队尾
if (s == null) {
s = new QNode(e, isData);
}
if (!t.casNext(null, s)) {
continue;
}
advanceTail(t, s);
// 4. 挂起当前线程
Object x = awaitFulfill(s, e, timed, nanos);
// 5. 当前线程被唤醒后,返回返回传递过来的节点值
if (x == s) {
clean(t, s);
return null;
}
if (!s.isOffList()) {
advanceHead(t, s);
if (x != null) {
s.item = s;
}
s.waiter = null;
}
return (x != null) ? (E) x : e;
} else {
// 6. 如果本次操作跟队尾节点模式不同,就从队头结点开始遍历,找到模式相匹配的节点
QNode m = h.next;
if (t != tail || m == null || h != head) {
continue;
} Object x = m.item;
// 7. 把当前节点值e传递给匹配到的节点m
if (isData == (x != null) || x == m ||
!m.casItem(x, e)) {
advanceHead(h, m);
continue;
}
// 8. 弹出队头节点,并唤醒节点m
advanceHead(h, m);
LockSupport.unpark(m.waiter);
return (x != null) ? (E) x : e;
}
}
}

SynchronousQueue的put方法底层源码的更多相关文章

  1. Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. 为什么很多类甚者底层源码要implements Serializable ?

    为什么很多类甚者底层源码要implements Serializable ? 在碰到异常类RuntimeException时,发现Throwable实现了 Serializable,还有我们平进的ja ...

  3. List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...

  4. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

  5. Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap

    声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...

  6. 2018.11.20 Struts2中对结果处理方式分析&struts2内置的方式底层源码剖析

    介绍一下struts2内置帮我们封装好的处理结果方式也就是底层源码分析 这是我们的jar包里面找的位置目录 打开往下拉看到result-type节点 name那一列就是我们的type类型取值 上一篇博 ...

  7. HashMap和ConcurrentHashMap的区别,HashMap的底层源码

    HashMap本质是数组加链表,根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面. ConcurrentHashMap在HashMap的基础 ...

  8. 总结HashSet以及分析部分底层源码

    总结HashSet以及分析部分底层源码 1. HashSet继承的抽象类和实现的接口 继承的抽象类:AbstractSet 实现了Set接口 实现了Cloneable接口 实现了Serializabl ...

  9. LInkedList总结及部分底层源码分析

    LInkedList总结及部分底层源码分析 1. LinkedList的实现与继承关系 继承:AbstractSequentialList 抽象类 实现:List 接口 实现:Deque 接口 实现: ...

  10. Vector总结及部分底层源码分析

    Vector总结及部分底层源码分析 1. Vector继承的抽象类和实现的接口 Vector类实现的接口 List接口:里面定义了List集合的基本接口,Vector进行了实现 RandomAcces ...

随机推荐

  1. 项目PMP之二项目运行环境

    一.项目运行环境因素 项目内部:组织过程资产(OPA):用于治理和执行项目,可为正式与非正式 过程.政策和程序:由非项目内职能部门制定的,如PMO 组织知识库:项目进行中累计的信息文档,如经验.设计. ...

  2. C :文件

    一直没有系统学习过该章节,现参考<C语言程序设计 (第四版)谭浩强> C文件基本知识 什么是文件 文件名 文件的分类 文件缓冲区 文件类型指针 typedef struct { short ...

  3. fopen在VS中不安全的问题

    问题 fopen函数哎VS中使用,报错: error C4996: 'fopen': This function or variable may be unsafe. Consider using f ...

  4. 玩转云端 | 拥有HBlock这项“存储盘活绝技”,数据中心也能“热辣瘦身”!

    夏天马上就要到了,"瘦身"不光是特定人群的需求,也是数据中心的需求.构建轻量化.低碳化.高性价比的新型数据中心,更有效地支撑经济社会数字化转型,已成为业界主流趋势. 如何让数据中心 ...

  5. 上云有道 | 一图读懂天翼云边缘安全加速平台AccessOne!

    上云有道 | 一图读懂天翼云边缘安全加速平台AccessOne!

  6. 服务器通用背板管理(UBM)实现

    本文分享自天翼云开发者社区<服务器通用背板管理(UBM)实现>,作者: 乘风 一 UBM概述 通过SGPIO 进行 SAS 和 SATA 背板管理的 SCSI 机箱服务 (SES) 标准于 ...

  7. AllPairs工具助力正交表测试用例设计

    AllPairs工具助力正交表测试用例设计 正交表法是一种高效的测试方法,特别适用于软件测试中需要处理多个控件及其多种取值组合的情况.以下是对正交表法的详细解释: 一.正交表法概述 正交表法是一种利用 ...

  8. ABB机器人3HNE00313-1示教器黑屏故障维修

    随着工业自动化的快速发展,ABB机器人示教器在生产线上的应用越来越广泛.然而,在使用过程中,示教器偶尔也会出现故障,其中比较常见的一种是ABB工业机械手示教器黑屏故障. 一.ABB工业机器人示教盒黑屏 ...

  9. Linux - 禁ping & 开放访问端口

    适用于Linux操作系统 禁ping 1.编辑 /etc/sysctl.conf 文件,文件末尾增加如下内容后,保存退出. net.ipv4.icmp_echo_ignore_all = 1 2.在命 ...

  10. CUDA与Cython之BatchGather

    技术背景 在前面一篇文章中,我们介绍过Cython+CUDA框架下实现一个简单的Gather算子的方法.这里演示Gather算子的升级版本实现--BatchGather算子.不过这里只是加了一个Bat ...