SynchronousQueue:零容量的同步传输神器

SynchronousQueue 是一个零容量的阻塞队列,不存储任何元素。每个插入操作(put)必须等待对应的移除操作(take),反之亦然。它的实现依赖于线程的阻塞和唤醒机制(通过LinkedTransferQueue 管理等待的线程),但这些是线程调度层面的结构,而非数据元素的存储空间。

LinkedTransferQueue详细分析见:LinkedTransferQueue核心设计与高效并发实现-CSDN博客

SynchronousQueue 通过继承和扩展 LinkedTransferQueue,巧妙地实现了零容量同步队列:

  1. 借用核心机制:利用 DualNodexfer、CAS操作等基础设施
  2. 扩展LIFO能力:在 Transferer 中实现栈模式的 xferLifo 方法
  3. 简化接口语义:所有容量方法返回0,体现零容量特性
  4. 双模式支持:公平模式使用队列,非公平模式使用栈(LIFO,更容易,且满足局部性原理;默认不公平)

这种设计既复用了 LinkedTransferQueue 的成熟并发机制,又通过扩展实现了独特的同步传输语义。

核心结构设计

主要组件

public class SynchronousQueue<E> extends AbstractQueue<E> 
    implements BlockingQueue<E>, java.io.Serializable {
    
    private final transient Transferer<E> transferer;  // 核心传输器
    private final transient boolean fair;              // 公平性策略
}

零容量特性实现

所有容量相关方法都返回固定值:

public boolean isEmpty() { return true; }      // 始终为空
public int size() { return 0; }                // 始终为0
public int remainingCapacity() { return 0; }   // 无剩余容量
public boolean contains(Object o) { return false; } // 不包含任何元素

内部传输器 - Transferer

Transferer 是 LinkedTransferQueue 的扩展,专门用于支持 LIFO(栈)模式:

static final class Transferer<E> extends LinkedTransferQueue<E> {
    final Object xferLifo(Object e, long ns) { ... }  // LIFO传输逻辑
    private void unspliceLifo(DualNode s) { ... }     // LIFO清理逻辑
}

利用 LinkedTransferQueue 的核心能力

SynchronousQueue 充分利用了 LinkedTransferQueue 的 DualNode 双端节点设计

  • 数据节点 (isData = true):生产者等待消费者
  • 请求节点 (isData = false):消费者等待生产者

核心传输方法

private Object xfer(Object e, long nanos) {
    Transferer<E> x = transferer;
    return (fair) ? x.xfer(e, nanos) : x.xferLifo(e, nanos);
}

策略选择

  • fair = true:使用 LinkedTransferQueue.xfer() - FIFO队列模式
  • fair = false:使用 Transferer.xferLifo() - LIFO栈模式

借用的关键能力

LinkedTransferQueue 能力SynchronousQueue 用途
DualNode 双端节点机制区分生产者/消费者等待节点
xfer() 核心传输方法公平模式下的 FIFO 传输
cmpExHead/cmpExTail CAS操作线程安全的头尾指针更新
await() 阻塞等待机制线程阻塞与唤醒
matched() 匹配检测判断节点是否已配对

LIFO 模式实现

final Object xferLifo(Object e, long ns) {
    boolean haveData = (e != null);
    Object m;
    outer: for (DualNode s = null, p = head;;) {
        // 1. 寻找互补节点进行匹配
        while (p != null) {
            if (isData == haveData) break;     // 同类型,跳出寻找
            if (p.cmpExItem(m, e) != m) {      // 尝试匹配
                // 匹配成功,唤醒等待线程
                LockSupport.unpark(p.waiter);
                break outer;
            }
        }
        
        // 2. 无匹配时推入新节点并等待
        if (s == null) s = new DualNode(e, haveData);
        s.next = p;
        if (p == cmpExHead(p, s)) {
            m = s.await(e, ns, this, p == null || p.waiter == null);
        }
    }
    return m;
}

 该函数实现了栈式的双重匹配算法,核心思想是:

  • 生产者消费者直接在栈顶进行匹配
  • 无匹配时将节点压入栈顶等待
  • 匹配成功后弹出对应节点

逐段代码分析

1. 初始化和匹配循环

boolean haveData = (e != null);
Object m;                              // the match or e if none
outer: for (DualNode s = null, p = head;;) {
  • haveData:标识当前操作类型(生产/消费)
  • p = head:从栈顶开始处理
  • s:延迟创建的新节点

2. 栈顶节点处理

while (p != null) {
    boolean isData; DualNode n, u; // help collapse
    if ((isData = p.isData) != ((m = p.item) != null))
        p = (p == (u = cmpExHead(p, (n = p.next)))) ? n : u;

已匹配节点清理

  • 检测不一致状态:isData != (item != null)表示节点已被匹配
  • 原子性地弹出已匹配节点,更新栈顶
  • 关键优化:协助其他线程清理,减少竞争
匹配逻辑判断
else if (isData == haveData)   // same mode; push below
    break;
else if (p.cmpExItem(m, e) != m)
    p = head;                  // missed; restart
else {                         // matched complementary node
    Thread w = p.waiter;
    cmpExHead(p, p.next);
    LockSupport.unpark(w);
    break outer;
}

三种情况处理

  1. 同类型节点:无法匹配,跳出内循环准备入栈
  2. 匹配失败:并发冲突,重新从栈顶开始
  3. 匹配成功:弹出节点,唤醒等待线程,完成交换
节点入栈和等待
if (ns == 0L) {                    // no match, no wait
    m = e;
    break;
}
if (s == null)                     // try to push node and wait
    s = new DualNode(e, haveData);
s.next = p;
if (p == (p = cmpExHead(p, s))) { //成功加入等待栈
    if ((m = s.await(e, ns, this,  // spin if (nearly) empty
                     p == null || p.waiter == null)) == e)
        unspliceLifo(s);           // cancelled
    else if (m != null)
        s.selfLinkItem();
    break;
}

入栈等待流程

  1. 立即模式检查ns == 0L时不等待直接返回 【匹配了会直接推出 outer 循环】
  2. 构建新节点:延迟创建,减少不必要的对象分配
  3. 栈顶插入s.next = p建立链接,CAS更新栈顶
  4. 等待匹配:调用await方法,支持超时和中断
  5. 清理处理:取消时调用unspliceLifo清理节点

    操作流程分析

    put/offer 操作流程

    1. 验证元素非空Objects.requireNonNull(e)
    2. 调用传输方法xfer(e, timeout)
    3. 等待匹配:如果没有消费者,生产者线程阻塞
    4. 成功传输:匹配到消费者时直接传递元素

    take/poll 操作流程

    1. 调用传输方法xfer(null, timeout)
    2. 寻找生产者:查找等待的生产者节点
    3. 获取元素:从生产者节点获取元素并唤醒生产者
    4. 返回结果:成功获取元素或超时返回null

    设计优势与应用场景

    核心优势

    • 零开销存储:无内部缓冲区,内存效率极高
    • 同步传输:确保生产者与消费者直接交互
    • 公平性可选:支持FIFO公平模式和LIFO非公平模式
    • 高性能:基于CAS操作,避免重锁

    典型应用场景

    • 线程池任务传递Executors.newCachedThreadPool() 内部使用
    • 生产者-消费者直接交换:需要同步handoff的场景
    • CSP通信模式:类似Go语言channel的同步通信
    import java.util.concurrent.SynchronousQueue;
    
    public class SyncQueueExample {
        public static void main(String[] args) {
            SynchronousQueue<String> queue = new SynchronousQueue<>();
            
            // 生产者线程
            new Thread(() -> {
                try {
                    String data = "Data-" + System.currentTimeMillis();
                    System.out.println("Producing: " + data);
                    queue.put(data); // 阻塞直到有消费者取走
                    System.out.println("Produced: " + data);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
            
            // 消费者线程
            new Thread(() -> {
                try {
                    Thread.sleep(1000); // 模拟处理延迟
                    String data = queue.take(); // 阻塞直到有数据可取
                    System.out.println("Consumed: " + data);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值