LinkedBlockingDeque详解

4.2 双向链表结构图

描述:

本图展示了包含三个节点(A、B和C)的一个典型双向链表。每个节点通过前驱(prev)和后继(next)指针相互连接,形成一个完整的双向循环。

graph LR;
    A((Node A)) --> B((Node B));
    A -->|prev| null;
    A -->|next| B;

    B((Node B)) --> C((Node Node C));
    B -->|prev| A;
    B -->|next| C;

    C((Node C)) --> A;
    C -->|prev| B;
    C -->|next| null;

    first(A);
    last(C);

解释:

  1. 节点A(首节点)

    • prev为null
    • next指向节点B
  2. 节点B(中间节点)

    • prev指向前一个节点,即Node A
    • next指向后一个节点,即Node C
  3. 节点C(尾节点)

    • prev指向其前一个节点,即Node B
    • next为null

队列状态:

  • 当队列为空时:

    • first和last均指null
  • 当只有一个元素时:

    • first和last指向同一个节点(假设该唯一节点是节点A)
    • 节点的prev和next均为自身

4.3 双向链表插入操作图

描述:

本图展示了将一个新元素(E)插入到已有的双向链表中。我们分别演示了在首部(putFirst(E))和尾部(putLast(E))插入元素。

graph LR;
    A((Node A)) --> B((Node B));
    E((New Node E)) -->|prev| null;
    E -->|next| A;

    subgraph "After putFirst(E)"
        F((Node F)) --> E;
        E --> A;
        A --> B;
    end

    C((Node C)) --> A;
    C -->|prev| B;
    C -->|next| null;

解释:

  1. 插入新元素E到首部(putFirst(E))

    • 新节点E的prev为null
    • E的next指向前一个首节点A
    • 更新链表头first指向E
  2. 结构更新后

    • 原来的首节点A,现在其prev指向新插入的E
    • 链表仍由F、E、A和B构成(假设F是新的第一个元素)
graph LR;
    A((Node A)) --> B((Node B));
    C((Node C)) --> A;

    subgraph "After putLast(D)"
        D((New Node D)) -->|prev| C;
        D --> null;
        C --> D;
    end

解释:

  1. 插入新元素D到尾部(putLast(E))
    • 新节点E的next为null
    • E的prev指向前一个尾节点C
    • 更新链表尾last指向E

4.4 双向链表移除操作图

描述:

本图展示了从双向链表中删除首元素(takeFirst()) 和尾部元素 ( takeLast() ) 的过程。

graph LR;
    A((Node A)) --> B((Node B));
    C((Node C)) --> A;

    subgraph "After takeFirst()"
        E((Node B))
        F((Node A))
        G((Null))
    end

解释:

  1. 移除首元素(takeFirst())
    • 更新first为B
    • 将A的next指向null(帮助GC)
graph LR;
    A((Node A)) --> B((Node B));
    C((Node C)) --> A;

    subgraph "After takeLast()"
        D((Node C))
        E((Null))
        F((B -> Node A))
    end

解释:

  1. 移除尾部元素(takeLast())
    • 更新last为A
    • 将C的prev设为null,帮助GC回收节点C

4.5 线程安全操作图

本部分展示了在多线程环境下,两个线程尝试同时进行插入和移除操作时的情况。由于使用单锁机制,所有操作必须按顺序执行。

graph LR;
    T1{Thread 1: putFirst(E)}
    T2{Thread 2: takeLast()}

    subgraph "Initial State"
        A((Node A)) --> B((Node B));
        C((Node C)) --> A;
    end

    T1 -->|Lock Acquired| Add E to first
    T2 -->|Wait on Lock| Wait for Lock Release

    subgraph "After putFirst(E)"
        D((New Node E)) -->|prev| null;
        D --> A;
    end

    T1 -->|Release lock| Unlock

    T2 -->|Lock Acquired| Take last item
    T2 -->|Remove C| Remove C from list

解释:

  • 初始状态

    • 节点A、B和C组成双向链表,first指向Node A,last指向Node C。
  • 线程T1尝试插入元素E到首部

    • T1获取锁,并将新节点E添加为新的首节点。
  • 线程T2等待

    • T2试图移除尾部的C但无法获得锁,必须等待T1完成操作并释放锁。
  • 线程T1释放锁

    • 插入完成后释放锁,使T2能够获得锁并继续执行。
  • 线程T2获取锁后移除元素C

    • T2获取锁,并从双向链表中移除尾部的节点C。更新last指向Node A。

总结:

在LinkedBlockingDeque的设计中,单锁机制确保了所有操作的一致性,尽管这可能会导致性能上的降低(比如多线程环境下的竞争)。然而,在双端队列的应用场景下,这种设计能够提供更高的可靠性和简洁性。

这篇文档详细地介绍了 Java LinkedBlockingDeque 类的各种特性和性能特点,并提供了许多有用的指导和示例代码。以下是对该文档的一些关键点总结以及建议:

关键点总结

  1. 设计概述

    • LinkedBlockingDeque 是一个基于双向链表的阻塞队列,实现了 BlockingDeque 接口。
    • 它支持高效的双端操作(头部和尾部插入/删除)。
  2. 主要特性

    • 可选有界:通过构造函数参数设置容量大小,默认为无界。
    • 单锁机制:所有操作都使用同一个 ReentrantLock 保证线程安全,这意味着所有操作都是串行处理的,可能会造成一定的性能瓶颈。
    • 支持双端阻塞队列和栈操作(如 putFirst, takeLast)。
  3. 适用场景

    • 当需要支持工作窃取、双端操作时使用。
    • 不适合纯 FIFO 场景,因为并发吞吐量较低,不如 LinkedBlockingQueue 高效。
  4. 性能分析

    • 吞吐量受单锁机制的限制,在高并发情况下可能会有瓶颈。
    • 内存占用较高,每个节点包含三个引用(对象本身、前驱和后继),而 LinkedBlockingQueue 仅需两个引用(对象本身和后继)。
  5. 注意事项

    • 默认无界可能导致 OOM,请确保在创建时指定合理的容量。
    • 使用 remove(Object) 方法会遍历整个队列,持有锁的时间较长,尽量避免频繁使用。
    • 迭代器是弱一致性的,在迭代过程中修改队列不会抛出异常,但结果可能过期。

使用建议

  1. 选择合适的阻塞队列

    • 如果只需要单向 FIFO 队列且对性能要求较高,请考虑 LinkedBlockingQueue。
    • 对于需要双端操作的场景或工作窃取算法,则需要使用 LinkedBlockingDeque。
    • 当容量固定并且内存占用是关键因素时,可以考虑使用 ArrayBlockingQueue。
  2. 合理设置队列容量

    • 除非业务明确不需要限制队列大小,并且已经充分测试和控制了流量,否则请给队列设置一个合理的最大容量以防止 OOM。
  3. 性能优化技巧

    • 使用 drainTo() 方法批量转移元素,减少锁竞争的机会。
    • 监控队列的大小并定期检查,确保在达到容量阈值之前进行适当的处理。
  4. 避免常见陷阱

    • 尽量避免在迭代过程中修改队列内容,可以考虑预先复制队列内容或者使用其他线程安全的数据结构。
    • 注意捕获和处理 InterruptedException 异常,特别是在等待条件下被中断时需要正确处理以恢复正常的程序流程。

> 🔗 相关阅读LinkedBlockingDeque