继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

LinkedTransferQueue 详解

程序员爱分享
关注TA
已关注
手记 32
粉丝 1
获赞 3

## 1. 概述


`LinkedTransferQueue` 是 Java 并发包中一个强大且复杂的阻塞队列实现。它实现了 `TransferQueue` 接口,融合了 `SynchronousQueue` 的**直接传递**(handoff)特性和 `LinkedBlockingQueue` 的**缓冲**能力。简单来说,它既允许生产者将元素放入队列供消费者后续获取,也支持生产者阻塞等待消费者直接取走元素,而不经过队列的“中间缓存”。


### 核心特点


-   **无界队列**:基于链表实现,理论上容量受限于内存,永远不会因容量满而阻塞生产者(`put` 永远不会阻塞)。

-   **支持 transfer 语义**:生产者可以通过 `transfer(e)` 阻塞等待,直到某个消费者取走该元素;`tryTransfer(e)` 可尝试立即传递,失败则立即返回而不入队。

-   **无锁算法**:内部大量使用 `CAS`(Compare-And-Swap)操作,避免使用显式锁(如 `ReentrantLock`),在高并发场景下提供更好的性能。

-   **公平 FIFO 匹配**:匹配等待线程时,总是从队列头部(head)开始扫描,保证等待时间最长的线程先被服务,避免饥饿。

-   **双链表结构**:JDK 8 中 `Node` 包含 `prev` 和 `next` 指针,便于快速移除已匹配的节点,相比 JDK 7 的单链表有较大改进。


### 典型应用场景


-   **生产者需要确认消费者已接收**:例如消息中间件中,发送端要求确保消息被接收端处理后才继续后续操作。

-   **高吞吐量的数据交换**:无锁设计减少了线程上下文切换,适合多生产者多消费者环境。

-   **实现轻量级的 Exchanger**:利用 `transfer` 的配对行为,两个线程可以安全地交换数据。


### 与其他队列的主要区别


| 队列                      | 容量         | 锁机制                  | 是否支持 transfer   | 特点                             |

| ----------------------- | ---------- | -------------------- | --------------- | ------------------------------ |

| **LinkedTransferQueue** | 无界         | 无锁(CAS)              | 是               | 可缓冲可 handoff,公平 FIFO,高吞吐       |

| **LinkedBlockingQueue** | 可选有界(默认无界) | 双锁(takeLock/putLock) | 否               | 有锁,size O(1),适合传统生产者-消费者缓冲     |

| **SynchronousQueue**    | 0(零容量)     | 无锁(CAS)              | 是(本质就是 handoff) | 每一个 put 必须等待一个 take,无缓冲,常用于线程池 |

| **ArrayBlockingQueue**  | 固定有界       | 单锁                   | 否               | 数组结构,有界,可指定公平性                 |


## 2. 核心方法说明


下表列出了 `LinkedTransferQueue` 的主要方法及其行为特征(基于 JDK 8)。


| 方法                                                  | 参数                            | 返回值                                              | 阻塞行为                         | 异常                                             |

| --------------------------------------------------- | ----------------------------- | ------------------------------------------------ | ---------------------------- | ---------------------------------------------- |

| `LinkedTransferQueue()`                             | 无                             | 构造器                                              | 无                            | 无                                              |

| `LinkedTransferQueue(Collection<? extends E> c)`    | `c`:初始集合                      | 构造器,将集合元素加入队列                                    | 无                            | `NullPointerException`                         |

| `put(E e)`                                          | `e`:元素                        | `void`                                           | 不阻塞(无界),内部调用 `offer`         | `NullPointerException`                         |

| `offer(E e)`                                        | `e`:元素                        | `boolean`:总是返回 `true`(无界)                        | 不阻塞                          | `NullPointerException`                         |

| `offer(E e, long timeout, TimeUnit unit)`           | `e`:元素,`timeout`:超时,`unit`:单位 | `boolean`:总是返回 `true`(超时被忽略)                     | 不阻塞                          | `NullPointerException`                         |

| `take()`                                            | 无                             | `E`:队首元素                                         | 如果队列空,阻塞直到有元素                | `InterruptedException`                         |

| `poll()`                                            | 无                             | `E`:队首元素,空返回 `null`                              | 不阻塞                          | 无                                              |

| `poll(long timeout, TimeUnit unit)`                 | `timeout`:超时,`unit`:单位        | `E`:元素,超时后仍空返回 `null`                            | 等待指定时间                       | `InterruptedException`                         |

| `peek()`                                            | 无                             | `E`:队首元素(不移除),空返回 `null`                         | 不阻塞                          | 无                                              |

| `size()`                                            | 无                             | `int`:当前元素个数                                     | 无(弱一致性,需遍历链表,非 O(1))         | 无                                              |

| `remainingCapacity()`                               | 无                             | `int`:总是返回 `Integer.MAX_VALUE`                   | 无                            | 无                                              |

| `transfer(E e)`                                     | `e`:元素                        | `void`                                           | 阻塞直到该元素被消费者 `take`/`poll` 取走 | `InterruptedException`, `NullPointerException` |

| `tryTransfer(E e)`                                  | `e`:元素                        | `boolean`:有消费者等待则传递返回 `true`,否则返回 `false`(元素不入队) | 不阻塞                          | `NullPointerException`                         |

| `tryTransfer(E e, long timeout, TimeUnit unit)`     | `e`:元素,`timeout`:超时,`unit`:单位 | `boolean`:超时前被消费返回 `true`,否则 `false`             | 等待指定时间,期间可被中断                | `InterruptedException`, `NullPointerException` |

| `hasWaitingConsumer()`                              | 无                             | `boolean`:是否有消费者在等待                              | 不阻塞                          | 无                                              |

| `getWaitingConsumerCount()`                         | 无                             | `int`:等待消费者数量(近似值)                               | 不阻塞                          | 无                                              |

| `drainTo(Collection<? super E> c)`                  | `c`:目标集合                      | `int`:转移的元素数量                                    | 无                            | `NullPointerException`                         |

| `drainTo(Collection<? super E> c, int maxElements)` | `c`:目标集合,`maxElements`:最大转移数  | `int`:实际转移数                                      | 无                            | `NullPointerException`                         |


> 注意:由于队列无界,`offer` 系列方法永远不会返回 `false`(除非元素为 `null`)。超时版本的 `offer` 实际上忽略超时参数,行为与无参 `offer` 相同。


## 3. 核心原理与源码分析(基于 JDK 8)


### 3.1 数据结构


`LinkedTransferQueue` 的核心字段(定义在 `java.util.concurrent.LinkedTransferQueue` 中):


```

java

 体验AI代码助手

 代码解读

复制代码

public class LinkedTransferQueue<E> extends AbstractQueue<E>

    implements TransferQueue<E>, java.io.Serializable {

    

    private transient volatile Node head;   // 队首节点

    private transient volatile Node tail;   // 队尾节点

    private transient volatile int sweepVote; // 用于帮助 GC 的投票计数器

    // ... 其他字段

}

```


节点 `Node` 是内部静态类,采用**双链表**结构:


```

java

 体验AI代码助手

 代码解读

复制代码

static final class Node {

    final boolean isData;   // true 表示数据节点(生产者),false 表示请求节点(消费者)

    volatile Object item;   // 数据节点的元素,请求节点为 null

    volatile Node next;     // 后继指针

    volatile Node prev;     // 前驱指针(JDK 8 新增,便于快速移除)

    volatile Thread waiter; // 等待线程(阻塞时记录)

    

    // 构造方法

    Node(Object item, boolean isData) {

        this.item = item;

        this.isData = isData;

    }

    // ...

}

```


-   **`isData`**:区分节点类型。生产者节点(`isData=true`)携带元素;消费者节点(`isData=false`)携带 `null`。

-   **`item`**:生产者节点存储实际元素,消费者节点为 `null`。当节点被匹配后,`item` 会被 CAS 设置为 `null`(数据节点)或非 `null`(请求节点),以表示“已取消/已匹配”。

-   **`prev/next`**:双链表指针,便于在匹配成功后从链表中快速移除节点(JDK 7 单链表需要从头遍历找到前驱,效率较低)。

-   **`waiter`**:阻塞的线程对象,匹配成功后会被唤醒。


双链表的引入大幅提升了移除节点的效率,是 JDK 8 的重要优化。


### 3.2 xfer 统一核心方法


所有入队、出队、传递操作最终都调用一个私有方法 `xfer`:


```

java

 体验AI代码助手

 代码解读

复制代码

private E xfer(E e, boolean haveData, int how, long nanos)

```


-   **参数 `e`**:元素(生产者传递非 `null`,消费者传递 `null`)。


-   **参数 `haveData`**:`true` 表示生产者操作(数据节点),`false` 表示消费者操作(请求节点)。


-   **参数 `how`**:操作模式,取值如下:


    -   `NOW` = 0:立即返回,不阻塞也不入队(用于 `poll`、`tryTransfer`)。

    -   `ASYNC` = 1:异步入队,不阻塞(用于 `put`、`offer`)。

    -   `SYNC` = 2:同步阻塞,直到匹配成功(用于 `take`、`transfer`)。

    -   `TIMED` = 3:带超时的阻塞(用于 `poll(timeout)`、`tryTransfer(timeout)`)。


-   **参数 `nanos`**:超时纳秒数,仅在 `how=TIMED` 时有效。


**返回值**:对于消费者操作,返回取到的元素;对于生产者操作,通常返回 `null`(但 `transfer` 成功时也返回 `null`)。


#### 主要流程


1.  **尝试匹配等待线程**  

    从 `head` 开始向后遍历,寻找一个**模式互补**(即 `haveData != node.isData`)的节点。如果找到,尝试通过 CAS 将节点的 `item` 设置为匹配值(生产者把 `item` 从元素改为 `null`,消费者把 `item` 从 `null` 改为元素)。


    -   CAS 成功:唤醒该节点的等待线程,将匹配节点从链表中断开(`unsplice`),并返回匹配的元素(消费者操作)或 `null`(生产者操作)。

    -   CAS 失败:说明已被其他线程匹配,继续向后遍历。


1.  **如果没有匹配且允许入队**(`how` 为 `ASYNC`/`SYNC`/`TIMED`)


    -   创建一个新节点(`isData=haveData`,`item=e`),并尝试将其追加到队尾(CAS 更新 `tail`)。

    -   如果 `how` 是 `ASYNC`,直接返回 `null`(生产者的 `put`/`offer` 结束)。

    -   如果 `how` 是 `SYNC` 或 `TIMED`,则进入自旋 + 阻塞等待,直到节点被匹配(`item` 被其他线程改变)或超时/中断。


1.  **阻塞等待细节**


    -   首先进行有限次数的自旋(`spins`),避免在持有锁(虽然是无锁,但自旋可减少上下文切换)的场景下立即阻塞。

    -   自旋未果则调用 `LockSupport.park(this)` 阻塞当前线程。

    -   被唤醒后检查匹配状态,如果成功则清理节点并返回,否则继续阻塞。


### 3.3 核心操作的 xfer 调用映射


| 公开方法                                            | 调用 xfer 方式                                        | 说明         |

| ----------------------------------------------- | ------------------------------------------------- | ---------- |

| `put(E e)`                                      | `xfer(e, true, ASYNC, 0)`                         | 异步入队,不阻塞   |

| `offer(E e)`                                    | `xfer(e, true, ASYNC, 0)`                         | 同上         |

| `offer(E e, long timeout, TimeUnit unit)`       | `xfer(e, true, ASYNC, 0)`(忽略超时)                   | 无界队列,超时无意义 |

| `take()`                                        | `xfer(null, false, SYNC, 0)`                      | 阻塞等待元素     |

| `poll()`                                        | `xfer(null, false, NOW, 0)`                       | 非阻塞,立即返回   |

| `poll(long timeout, TimeUnit unit)`             | `xfer(null, false, TIMED, unit.toNanos(timeout))` | 超时阻塞       |

| `transfer(E e)`                                 | `xfer(e, true, SYNC, 0)`                          | 阻塞直到被消费    |

| `tryTransfer(E e)`                              | `xfer(e, true, NOW, 0)`                           | 仅尝试匹配,不入队  |

| `tryTransfer(E e, long timeout, TimeUnit unit)` | `xfer(e, true, TIMED, unit.toNanos(timeout))`     | 超时等待被消费    |


### 3.4 公平性保证


`LinkedTransferQueue` 是**公平 FIFO** 的。匹配操作总是从 `head` 开始向后扫描第一个互补模式的节点。由于入队操作将新节点追加到 `tail`,等待时间最长的线程位于 `head` 附近,因此它们会优先被匹配。这避免了不公平的“插队”现象,保证了线程调度的公平性。


### 3.5 无锁算法核心


整个队列不使用 `synchronized` 或 `ReentrantLock`,而是依赖 `sun.misc.Unsafe` 提供的 CAS 原语。关键操作:


-   **`casHead` / `casTail`**:更新头尾指针。

-   **`casNext`**:设置节点的 `next` 指针。

-   **`casItem`**:设置节点的 `item` 字段,这是匹配的核心——一个节点一旦 `item` 被成功修改,就意味着匹配完成。


CAS 操作的原子性避免了多线程竞争时的数据不一致,同时不会导致线程阻塞(除了显式的 `park`)。


### 3.6 GC 优化:sweep 机制


由于节点被匹配后会从链表中移除,但移除操作本身也需要遍历链表。`LinkedTransferQueue` 使用一个 `sweepVote` 计数器来定期触发“清扫”操作,遍历链表并清除那些已经匹配但尚未断开的节点(比如因为并发原因未及时 `unsplice`)。这有助于减少内存占用,避免“垃圾节点”长期滞留。


### 3.7 与 SynchronousQueue 对比


-   **容量**:`LinkedTransferQueue` 可以缓存多个元素;`SynchronousQueue` 容量为 0,每个 `put` 必须配对 `take`。

-   **行为**:`LinkedTransferQueue` 的 `transfer(e)` 类似于 `SynchronousQueue` 的 `put(e)`,但前者在没有消费者时会将元素入队并阻塞,后者直接阻塞(不存储)。

-   **适用场景**:`SynchronousQueue` 适用于纯 handoff 场景(如 `Executors.newCachedThreadPool`);`LinkedTransferQueue` 更灵活,既支持 handoff 也支持缓冲。


## 4. 必要流程的 Mermaid 图


### 4.1 类图


head/tail


LinkedTransferQueue<E>


-head : Node


-tail : Node


-sweepVote : int


+put(E e) : void


+offer(E e) : boolean


+take() : E


+poll() : E


+transfer(E e) : void


+tryTransfer(E e) : boolean


+hasWaitingConsumer() : boolean


+getWaitingConsumerCount() : int


-xfer(E e, boolean haveData, int how, long nanos) : E


Node


-isData : boolean


-item : Object


-next : Node


-prev : Node


-waiter : Thread


+Node(Object item, boolean isData)


**描述**:`LinkedTransferQueue` 持有 `head` 和 `tail` 引用指向双链表节点。每个 `Node` 包含数据/请求标志、元素、前后指针以及等待线程。核心方法 `xfer` 统一处理各种操作模式。


### 4.2 链表结构图


Node3


mode=DATA


item=E2


Node2


mode=REQUEST (消费者)


item=null


Node1


mode=DATA (生产者)


item=E1


head


tail


prev=null


prev=Node1


prev=Node2


prev=Node3


**描述**: 该图展示了一个包含三个节点的双链表实例。


-   **`head`** 指向第一个节点(Node1),它是一个生产者节点(`isData=true`),携带元素 `E1`,`prev` 为 `null`。

-   **Node2** 是消费者节点(`isData=false`),`item` 为 `null`,表示它正在等待一个生产者来匹配。它的 `prev` 指向 Node1,`next` 指向 Node3。

-   **Node3** 是第二个生产者节点,携带元素 `E2`,`next` 指向 `tail`。

-   **`tail`** 是一个哨兵引用,指向最后一个节点(Node3)。注意,`tail` 并非总是指向真正的尾节点,在并发更新时可能滞后,但最终会通过 CAS 修正。


双链表的优势体现在:当 Node2 被匹配后,可以借助 `prev` 和 `next` 直接将其前后节点链接,而无需从头扫描找到前驱,时间复杂度从 O(n) 降为 O(1)。队列的公平性来源于匹配时总是从 `head` 开始扫描,因此 Node1(等待最久)会优先被匹配。


### 4.3 xfer 核心流程图(匹配阶段)


true 生产者


false 消费者




失败


成功


xfer 开始


haveData?


期望匹配消费者节点  

目标item==null


期望匹配生产者节点  

目标item!=null


从head开始扫描


找到互补模式节点?


无匹配,进入入队/返回


CAS 设置节点的item字段


成功?


继续向后扫描


唤醒节点上的等待线程


从链表中移除节点 (unsplice)


返回匹配的值  

消费者返回元素,生产者返回null


结束


**描述**: 该图描述了 `xfer` 方法中最关键的**匹配阶段**,即尝试与队列中已存在的等待线程进行“手递手”传递。


1.  **确定目标节点类型**:


    -   生产者(`haveData=true`)希望找到一个消费者节点,该节点的 `item` 当前为 `null`。

    -   消费者(`haveData=false`)希望找到一个生产者节点,该节点的 `item` 不为 `null`(携带具体元素)。


1.  **扫描策略**:从 `head` 开始,沿着 `next` 指针遍历链表,直到遇到 `null`(链表末尾)。


    -   之所以从 `head` 开始,是为了保证 **FIFO 公平性**:等待时间最长的节点(最靠近 `head`)优先被匹配,避免线程饥饿。


1.  **匹配尝试**:对于每个遍历到的节点,检查其 `isData` 是否与当前操作互补(即 `isData != haveData`)。


    -   如果互补,则通过 `CAS` 尝试修改该节点的 `item` 字段:


        -   生产者将消费者的 `item` 从 `null` 改为非 `null`(实际元素值)。

        -   消费者将生产者的 `item` 从非 `null` 改为 `null`。


    -   CAS 成功意味着当前线程赢得了匹配权;如果失败(说明另一个线程已经抢先匹配了该节点),则继续向后扫描。


1.  **匹配成功后的动作**:


    -   唤醒该节点上阻塞的线程(`LockSupport.unpark(waiter)`)。

    -   调用 `unsplice` 将匹配节点从双链表中移除,更新 `head` 或前后节点的指针。

    -   返回相应的值:消费者返回取到的元素,生产者返回 `null`(表示传递成功)。


1.  **未匹配到任何节点**:则进入入队逻辑(`how` 决定是否入队),或者对于 `NOW` 模式直接返回。


这个匹配过程是无锁的,多个线程可以同时尝试匹配不同的节点,CAS 保证了原子性。


### 4.4 入队与阻塞流程


ASYNC


NOW


SYNC


TIMED






匹配


超时/中断


xfer 无匹配


how 的值?


创建节点,追加到队尾  

立即返回


立即返回 null/false


创建节点,追加到队尾


创建节点,追加到队尾


有限次自旋  

检查是否被匹配


被匹配?


清理节点,返回


自旋次数耗尽?


LockSupport.park(this)


被唤醒或中断


匹配或超时/中断?


根据情况抛出异常或返回false


结束


**描述**:当匹配阶段未找到互补节点时,根据操作模式 `how` 决定后续行为。


-   **`ASYNC` 模式**(`put`、`offer`):  

    创建一个新节点(`isData=haveData`,`item=e`),通过 CAS 追加到 `tail` 后面,然后立即返回。因为队列无界,永远不会阻塞。


-   **`NOW` 模式**(`poll`、`tryTransfer`):  

    直接返回 `null`(消费者)或 `false`(生产者),**不会创建任何节点**。这体现了“非阻塞、仅尝试”的语义。


-   **`SYNC` 模式**(`take`、`transfer`)和 **`TIMED` 模式**(带超时的 `poll` 或 `tryTransfer`):


    1.  **创建并入队**:与 `ASYNC` 类似,先创建节点并追加到队尾。此时节点处于“等待匹配”状态。

    1.  **自旋等待**:为了避免昂贵的线程阻塞/唤醒开销,线程会先进行有限次数的**自旋**(`spins`,通常基于 CPU 核数动态计算)。自旋期间反复检查节点的 `item` 是否已被其他线程修改(即是否被匹配)。

    1.  **阻塞**:如果自旋次数耗尽仍未匹配,则调用 `LockSupport.park(this)` 将当前线程挂起。`TIMED` 模式会使用 `parkNanos` 并设置超时。

    1.  **唤醒与检查**:当被匹配线程唤醒(或超时/中断)后,再次检查节点状态。如果 `item` 已被修改(匹配成功),则调用 `cleanup` 将节点从链表中移除并返回;否则根据超时或中断标志抛出 `InterruptedException` 或返回 `false`。


自旋机制是性能优化的关键:在锁竞争不激烈或临界区极短的情况下,自旋能避免线程进入内核态,大幅降低延迟。


### 4.5 transfer 与 take 配对时序图


生产者线程LinkedTransferQueue消费者线程生产者阻塞transfer(e)xfer(e, true, SYNC)扫描head,无消费者节点创建数据节点,追加到tail自旋后 park()take()xfer(null, false, SYNC)从head扫描,找到生产者节点CAS 将节点item设为null唤醒生产者线程返回元素e解除阻塞清理节点transfer返回生产者线程LinkedTransferQueue消费者线程


**描述**:该时序图展示了典型的生产者-消费者通过 `transfer` 和 `take` 完成一次“握手”的全过程。


1.  **生产者调用 `transfer(e)`** :


    -   内部调用 `xfer(e, true, SYNC, 0)`。

    -   匹配阶段扫描链表,发现没有等待的消费者节点(队列可能为空或全是生产者节点)。

    -   创建一个新的数据节点(`isData=true`, `item=e`),通过 CAS 追加到 `tail`。

    -   由于 `how=SYNC`,节点入队后,生产者线程进入自旋,随后调用 `park()` 阻塞。


1.  **消费者调用 `take()`** :


    -   内部调用 `xfer(null, false, SYNC, 0)`。

    -   匹配阶段从 `head` 开始扫描,找到了生产者节点(`isData=true` 且 `item=e`),两者互补。

    -   消费者通过 CAS 将该节点的 `item` 从 `e` 修改为 `null`。CAS 成功,表示消费者赢得了匹配权。

    -   消费者唤醒生产者节点中保存的 `waiter` 线程(即生产者线程)。

    -   消费者返回被取出的元素 `e`,`take()` 结束。


1.  **生产者被唤醒**:


    -   从 `park()` 返回,检查到节点的 `item` 已被改为 `null`(匹配成功)。

    -   调用清理逻辑将节点从链表中移除。

    -   `transfer(e)` 返回 `void`,生产者继续执行。


注意:如果消费者在生产者之前调用 `take()`,则角色互换——消费者节点会先入队并阻塞,直到生产者到来并匹配它。整个机制是对称的。


### 4.6 tryTransfer 的快速路径




tryTransfer e


xfer e, true, NOW, 0


从 head 扫描消费者节点


找到等待消费者?


CAS 匹配节点  

唤醒消费者


返回 true


返回 false  

元素不入队


结束


**描述**:`tryTransfer` 是 `transfer` 的非阻塞版本,其行为由 `how=NOW` 决定。


-   **不会创建节点**:即使用户调用 `tryTransfer(e)` 时没有等待的消费者,元素 `e` **也不会被放入队列**。这与其他 `offer` 方法完全不同。


-   **仅尝试匹配已有消费者**:从 `head` 开始扫描,寻找一个消费者节点(`isData=false` 且 `item==null`)。


    -   如果找到,则通过 CAS 将该消费者的 `item` 从 `null` 设置为 `e`,唤醒消费者线程,并返回 `true`。

    -   如果未找到,立即返回 `false`,元素 `e` 被丢弃(应用层需要自行处理,例如重试、持久化)。


-   **不阻塞、不超时**:整个操作是纯 CPU 操作,不会导致线程挂起。


此方法适用于“如果消费者恰好正在等待,就传递;否则放弃”的场景,例如在实时系统中避免生产者因等待而阻塞。


### 4.7 多线程并发下的匹配与出队示意图


新生产者C到达


初始队列


模式互补


新生产者C  

调用put


与消费者节点A匹配


head


生产者节点B  

等待


tail


匹配?


移除节点A


head指向原N2


最终队列  

head->生产者节点B  

tail不变


**描述**:该图演示了多线程并发环境下,一个新生产者到来时如何与队列头部等待的消费者匹配,并更新链表结构。


-   **初始状态**:队列中有两个节点。


    -   节点 A:消费者(`isData=false`),处于等待状态。

    -   节点 B:生产者(`isData=true`),也处于等待状态(例如之前调用 `transfer` 但未匹配)。  

        `head` 指向 A,`tail` 指向 B。


-   **新生产者 C 调用 `put(e)`** :


    -   `put` 对应 `xfer(e, true, ASYNC, 0)`,首先执行匹配阶段。

    -   从 `head` 开始扫描,第一个节点 A 是消费者,与生产者 C 互补。

    -   生产者 C 尝试 CAS 修改节点 A 的 `item`(从 `null` 改为 `e`)。CAS 成功。

    -   唤醒节点 A 上阻塞的消费者线程,并将节点 A 从链表中移除(`unsplice`)。


-   **移除节点 A 的过程**:


    -   节点 A 的 `prev` 为 `null`(因为它是 `head`),`next` 指向节点 B。

    -   将 `head` 通过 CAS 从 A 更新为 B(节点 B 成为新的 `head`)。

    -   节点 B 的 `prev` 被设置为 `null`,断开了与 A 的链接。

    -   节点 A 不再被任何活跃引用,可被 GC 回收。


-   **最终队列**:`head` 指向节点 B(生产者),`tail` 仍指向 B(如果 B 是最后一个节点)。


这个例子展示了 **并发匹配和出队** 的典型流程:新来的生产者没有创建新节点,而是直接与已存在的消费者握手,并将消费者节点从队列中移除,实现了“手递手”且队列长度减少的效果。整个过程无锁,依赖 CAS 保证原子性。


* * *


## 5. 实际应用场景与代码举例(JDK 8 兼容)


以下所有示例均可在 JDK 8 环境下编译运行。假设类名为 `LinkedTransferQueueDemo`,请自行包含在同一个文件中。


### 5.1 生产者等待消费者确认(transfer)


**场景**:消息发送者必须确保消息被接收后才继续发送下一条,保证可靠交付。


```

java

 体验AI代码助手

 代码解读

复制代码

import java.util.concurrent.LinkedTransferQueue;

import java.util.concurrent.TransferQueue;


public class TransferConfirmDemo {

    public static void main(String[] args) throws InterruptedException {

        TransferQueue<String> queue = new LinkedTransferQueue<>();

        

        // 消费者线程

        Thread consumer = new Thread(() -> {

            try {

                String msg = queue.take();

                System.out.println("消费者收到: " + msg);

                // 模拟处理耗时

                Thread.sleep(500);

                System.out.println("消费者处理完成");

            } catch (InterruptedException e) {

                Thread.currentThread().interrupt();

            }

        });

        consumer.start();

        

        // 确保消费者先启动,或者不保证也可演示

        Thread.sleep(100);

        

        // 生产者使用 transfer,等待消费者取走

        System.out.println("生产者开始 transfer...");

        queue.transfer("重要消息");

        System.out.println("生产者确认消息已被消费,继续执行");

    }

}

```


**输出**(消费者稍后处理完成):


```

arduino

 体验AI代码助手

 代码解读

复制代码

生产者开始 transfer...

消费者收到: 重要消息

消费者处理完成

生产者确认消息已被消费,继续执行

```


### 5.2 尝试立即传递(tryTransfer)


**场景**:如果消费者未就绪,生产者立即放弃并执行备选逻辑(如记录日志、暂存数据库)。


```

java

 体验AI代码助手

 代码解读

复制代码

import java.util.concurrent.LinkedTransferQueue;

import java.util.concurrent.TransferQueue;


public class TryTransferDemo {

    public static void main(String[] args) {

        TransferQueue<String> queue = new LinkedTransferQueue<>();

        

        // 没有消费者启动

        boolean success = queue.tryTransfer("即时消息");

        if (success) {

            System.out.println("消息已被消费者接收");

        } else {

            System.out.println("无消费者等待,消息未送达,转存至数据库");

        }

        

        // 启动一个消费者

        new Thread(() -> {

            try {

                String msg = queue.take();

                System.out.println("消费者收到: " + msg);

            } catch (InterruptedException e) {

                Thread.currentThread().interrupt();

            }

        }).start();

        

        // 等待消费者进入等待状态

        try { Thread.sleep(100); } catch (InterruptedException e) {}

        

        // 再次尝试

        success = queue.tryTransfer("第二条消息");

        System.out.println("第二次尝试结果: " + success);

    }

}

```


**输出**:


```

makefile

 体验AI代码助手

 代码解读

复制代码

无消费者等待,消息未送达,转存至数据库

消费者收到: 第二条消息

第二次尝试结果: true

```


### 5.3 带超时的 tryTransfer


**场景**:生产者等待消费者一段时间,若无人接收则回退。


```

java

 体验AI代码助手

 代码解读

复制代码

import java.util.concurrent.LinkedTransferQueue;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.TransferQueue;


public class TryTransferTimeoutDemo {

    public static void main(String[] args) throws InterruptedException {

        TransferQueue<String> queue = new LinkedTransferQueue<>();

        

        // 生产者尝试等待 1 秒

        boolean success = queue.tryTransfer("超时消息", 1, TimeUnit.SECONDS);

        if (!success) {

            System.out.println("1秒内无消费者,消息进入备用队列");

        }

        

        // 启动一个延迟消费者

        new Thread(() -> {

            try {

                Thread.sleep(1500); // 晚于超时时间

                String msg = queue.take();

                System.out.println("消费者收到: " + msg);

            } catch (InterruptedException e) {}

        }).start();

        

        // 再次尝试,等待 3 秒

        success = queue.tryTransfer("延迟消息", 3, TimeUnit.SECONDS);

        System.out.println("第二次尝试结果: " + success);

    }

}

```


**输出**: [0](http://010-kfp.wikidot.com/) [1](http://021-kfp.wikidot.com/) [2](http://022kaifapiao.wikidot.com/) [3](http://023kaifapiao.wikidot.com/) [4](http://0351kaifapiao.wikidot.com/) [5](http://0471kaifapiao.wikidot.com/) [6](http://0311kaifapiao.wikidot.com/) [7](http://024kaifapiao.wikidot.com/) [8](http://0431kaifapiao.wikidot.com/) [9](http://0451kaifapiao.wikidot.com/)


```

makefile

 体验AI代码助手

 代码解读

复制代码

1秒内无消费者,消息进入备用队列

第二次尝试结果: true

消费者收到: 延迟消息

```


### 5.4 生产者-消费者缓冲模式(对比 LinkedBlockingQueue)


**场景**:大量数据生产消费,对比 `LinkedTransferQueue` 和 `LinkedBlockingQueue` 的吞吐量(本示例仅展示使用方式,实际性能测试需要更严谨的基准)。


```

java

 体验AI代码助手

 代码解读

复制代码

import java.util.concurrent.LinkedBlockingQueue;

import java.util.concurrent.LinkedTransferQueue;

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.atomic.AtomicLong;


public class ThroughputCompare {

    private static final int PRODUCERS = 2;

    private static final int CONSUMERS = 2;

    private static final int TASKS_PER_PRODUCER = 100_000;

    

    public static void main(String[] args) throws InterruptedException {

        // 测试 LinkedTransferQueue

        BlockingQueue<Integer> transferQueue = new LinkedTransferQueue<>();

        long time1 = testQueue(transferQueue);

        System.out.println("LinkedTransferQueue 耗时: " + time1 + " ms");

        

        // 测试 LinkedBlockingQueue

        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

        long time2 = testQueue(blockingQueue);

        System.out.println("LinkedBlockingQueue 耗时: " + time2 + " ms");

    }

    

    private static long testQueue(BlockingQueue<Integer> queue) throws InterruptedException {

        AtomicLong total = new AtomicLong();

        Thread[] producers = new Thread[PRODUCERS];

        Thread[] consumers = new Thread[CONSUMERS];

        

        // 消费者

        for (int i = 0; i < CONSUMERS; i++) {

            consumers[i] = new Thread(() -> {

                try {

                    while (true) {

                        Integer v = queue.take();

                        if (v == -1) break; // 终止信号

                        total.incrementAndGet();

                    }

                } catch (InterruptedException e) {

                    Thread.currentThread().interrupt();

                }

            });

            consumers[i].start();

        }

        

        long start = System.currentTimeMillis();

        // 生产者

        for (int i = 0; i < PRODUCERS; i++) {

            final int id = i;

            producers[i] = new Thread(() -> {

                try {

                    for (int j = 0; j < TASKS_PER_PRODUCER; j++) {

                        queue.put(j);

                    }

                } catch (InterruptedException e) {

                    Thread.currentThread().interrupt();

                }

            });

            producers[i].start();

        }

        

        // 等待生产者完成

        for (Thread p : producers) p.join();

        // 发送终止信号

        for (int i = 0; i < CONSUMERS; i++) queue.put(-1);

        for (Thread c : consumers) c.join();

        

        long end = System.currentTimeMillis();

        System.out.println("处理数量: " + total.get());

        return end - start;

    }

}

```


**注意**:实际运行中 `LinkedTransferQueue` 通常表现更优,但结果受 JVM、CPU 核数等影响。


### 5.5 获取等待消费者信息


**场景**:监控系统动态调整生产速率,避免过度生产。


```

java

 体验AI代码助手

 代码解读

复制代码

import java.util.concurrent.LinkedTransferQueue;

import java.util.concurrent.TransferQueue;


public class MonitorDemo {

    public static void main(String[] args) throws InterruptedException {

        TransferQueue<String> queue = new LinkedTransferQueue<>();

        

        // 启动消费者,但消费慢

        Thread slowConsumer = new Thread(() -> {

            try {

                while (true) {

                    String item = queue.take();

                    System.out.println("消费: " + item);

                    Thread.sleep(500); // 模拟慢消费

                }

            } catch (InterruptedException e) {}

        });

        slowConsumer.setDaemon(true);

        slowConsumer.start();

        

        // 监控线程

        Thread monitor = new Thread(() -> {

            while (true) {

                System.out.printf("等待消费者数: %d, 队列大小: %d%n",

                        queue.getWaitingConsumerCount(), queue.size());

                try { Thread.sleep(1000); } catch (InterruptedException e) {}

            }

        });

        monitor.setDaemon(true);

        monitor.start();

        

        // 生产者生产 10 个元素

        for (int i = 1; i <= 10; i++) {

            queue.put("消息" + i);

            System.out.println("生产: 消息" + i);

            Thread.sleep(100);

        }

        

        Thread.sleep(3000); // 让监控输出一会

    }

}

```


**输出片段**:


```

makefile

 体验AI代码助手

 代码解读

复制代码

等待消费者数: 1, 队列大小: 0

生产: 消息1

消费: 消息1

生产: 消息2

等待消费者数: 1, 队列大小: 0

消费: 消息2

...

```


## 6. 吞吐量与性能分析


### 6.1 无锁算法的性能优势


-   **无上下文切换**:传统锁(如 `LinkedBlockingQueue` 的 `ReentrantLock`)在竞争激烈时会导致线程阻塞和唤醒,引发大量上下文切换。`LinkedTransferQueue` 基于 CAS,大多数操作在用户态自旋完成,失败时也仅短暂阻塞(通过 `LockSupport.park`),整体上减少了内核态切换。

-   **更高的并发度**:多线程可以同时尝试入队、出队,不同节点上的 CAS 操作互不干扰(例如,`tail` 的更新和 `head` 的更新可以并发进行),而双锁队列的 `takeLock` 和 `putLock` 虽然分离,但仍有竞争。


### 6.2 节点匹配的扫描开销


-   **最坏情况 O(n)** :当队列中有大量等待节点时,每次 `xfer` 都需要从 `head` 开始扫描,直到找到互补节点或到达 `tail`。极端情况下(如所有节点都是同一模式),扫描会遍历整个队列,导致性能下降。

-   **实际表现**:通常队列长度不会太大(因为匹配会及时移除节点),且扫描是内存局部性较好的链表遍历,开销可控。此外,JDK 8 中的双链表和启发式跳过(如 `sweep`)优化了扫描效率。


### 6.3 内存占用


-   每个元素包装为一个 `Node` 对象,包含 `prev`、`next`、`item`、`waiter`、`isData` 等字段。相比 `LinkedBlockingQueue` 的 `Node`(只有 `next` 和 `item`),内存开销更大(多了 `prev` 和 `waiter`)。

-   相比 `SynchronousQueue` 的节点(`TransferStack` 或 `TransferQueue` 节点),两者复杂度相近,但 `SynchronousQueue` 的节点数量与并发线程数相关,而 `LinkedTransferQueue` 的节点数量与未匹配元素数相关。


### 6.4 与 LinkedBlockingQueue 对比


| 维度          | LinkedTransferQueue | LinkedBlockingQueue     |

| ----------- | ------------------- | ----------------------- |

| 锁机制         | 无锁(CAS)             | 双锁(takeLock/putLock)    |

| 吞吐量(高并发)    | 通常更高                | 锁竞争可能成为瓶颈               |

| size() 操作   | O(n),弱一致            | O(1),维护一个 AtomicInteger |

| transfer 支持 | 是                   | 否                       |

| 内存占用(每元素)   | 较大(双链表 + waiter)    | 较小(单链表)                 |


### 6.5 与 SynchronousQueue 对比


-   **纯 handoff 场景**:`SynchronousQueue` 可能更快,因为它不维护链表结构(采用栈或队列,但节点数不超过并发线程数),匹配操作更直接。

-   **混合场景**:`LinkedTransferQueue` 更灵活,既能缓冲又能直接传递,且公平模式下 `SynchronousQueue` 的队列实现本质上也是链表,性能差异不大。


### 6.6 性能调优建议


1.  **避免频繁调用 `size()`** :该方法需要遍历整个链表,时间复杂度 O(n),且返回值是弱一致的(不代表精确大小)。可使用 `isEmpty()` 判断空。

1.  **谨慎使用 `transfer`**:如果消费者速度跟不上,生产者会永久阻塞,容易导致线程池耗尽。建议配合超时或使用 `tryTransfer`。

1.  **对于纯缓冲场景**:使用 `put`/`take` 即可,无需启用 transfer 语义。

1.  **合理设置并发度**:虽然是无锁队列,但过多的线程同时竞争同一个 `head`/`tail` 的 CAS 仍然会导致自旋重试,适当限制并发数可提升性能。


## 7. 注意事项与常见陷阱


| 注意事项                                               | 原因和解决方案                                                             |

| -------------------------------------------------- | ------------------------------------------------------------------- |

| **`size()` 是 O(n) 操作**                             | 需要遍历整个链表才能计数,高并发下频繁调用会严重影响性能。使用 `isEmpty()` 替代判空。                   |

| **`remainingCapacity()` 永远返回 `Integer.MAX_VALUE`** | 因为队列无界,不要依赖此方法做容量控制。                                                |

| **`transfer` 可能永久阻塞**                              | 如果没有消费者调用 `take`/`poll`,生产者会一直阻塞。应使用 `tryTransfer` 或带超时的版本,或设计消费保证。 |

| **`tryTransfer` 不保证元素入队**                          | 返回 `false` 时元素未被任何线程接收,需要应用层自行处理(如重试、持久化)。                          |

| **`poll` 和 `peek` 可能返回 `null`**                    | 正确判空,避免 `NullPointerException`。                                     |

| **公平性带来的性能权衡**                                     | FIFO 公平匹配比 LIFO(如某些栈实现)略慢,但避免了线程饥饿。                                 |

| **无锁算法导致的调试复杂性**                                   | 内部实现非常复杂,普通开发者不应修改;使用时只需关注 API。                                     |

| **元素不可为 `null`**                                   | 与大多数 `BlockingQueue` 实现一致,插入 `null` 会抛出 `NullPointerException`。     |

| **内存可见性由 CAS 保证**                                  | 不需要额外加 `volatile` 或同步,`LinkedTransferQueue` 已确保线程安全。                |


## 8. 与其他阻塞队列的对比总结


| 队列                      | 有界性        | 数据结构 | 锁机制                 | 支持 transfer   | size 复杂度 | 公平性         | 典型应用场景                                  |

| ----------------------- | ---------- | ---- | ------------------- | ------------- | -------- | ----------- | --------------------------------------- |

| **LinkedTransferQueue** | 无界         | 双链表  | 无锁(CAS)             | 是             | O(n)     | 公平 FIFO     | 高吞吐、需要确认送达、缓冲 + handoff 混合              |

| **LinkedBlockingQueue** | 可选有界(默认无界) | 单链表  | 双锁(take/put 分离)     | 否             | O(1)     | 非公平(默认)     | 传统生产者-消费者缓冲,容量可限制                       |

| **SynchronousQueue**    | 0(零容量)     | 栈/队列 | 无锁(CAS)             | 是(本质 handoff) | O(1)     | 可配置(公平/非公平) | 线程池 handoff(如 `CachedThreadPool`),无缓冲交换 |

| **ArrayBlockingQueue**  | 固定有界       | 数组   | 单锁(`ReentrantLock`) | 否             | O(1)     | 可配置(公平/非公平) | 固定大小缓冲区,资源受限场景                          |


**补充说明**:


-   `LinkedTransferQueue` 的 `size()` 复杂度为 O(n),而其他三种队列都可以在 O(1) 时间内返回大小(`SynchronousQueue` 总是返回 0)。

-   `SynchronousQueue` 的公平模式使用队列(FIFO),非公平模式使用栈(LIFO);`LinkedTransferQueue` 固定为 FIFO。

-   `ArrayBlockingQueue` 使用单锁,在生产和消费同时进行时有一定竞争,但数组结构缓存友好。


## 9. 总结与学习指引


### 核心特点回顾


`LinkedTransferQueue` 是 Java 并发工具包中设计精妙的无锁阻塞队列,其独特之处在于:


-   **无界双链表**:动态扩展,永不阻塞生产者(除 `transfer` 外)。

-   **统一 `xfer` 模型**:所有操作都通过一个核心方法实现,代码复用度高。

-   **无锁 + CAS**:避免锁竞争,提高并发吞吐量。

-   **transfer 语义**:填补了普通队列和 `SynchronousQueue` 之间的空白,提供生产者驱动的确认机制。

-   **公平 FIFO**:保证先到先服务,防止饥饿。


### 使用建议


-   **需要生产者确认消费**:使用 `transfer` 或带超时的 `tryTransfer`。

-   **高吞吐量缓冲**:使用 `put` / `take`,比 `LinkedBlockingQueue` 通常有更好的伸缩性。

-   **避免依赖 `size()`** :其 O(n) 开销和弱一致性可能误导业务逻辑。

-   **处理 `tryTransfer` 失败**:实现回退策略,避免数据丢失。

-   **与 `SynchronousQueue` 取舍**:纯 handoff 且不需要缓存时,`SynchronousQueue` 可能更轻量;需要缓冲或混合模式时,选 `LinkedTransferQueue`。


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP