Java基础 集合框架 队列架构 阻塞双端队列BlockingDeque架构

BlockingDeque

  • BlockingDeque 核心特性
  • BlockingDeque 核心方法
  • 唯一标准实现:LinkedBlockingDeque
    • LinkedBlockingDeque 构造方法
    • LinkedBlockingDeque 数据结构及管理逻辑
    • LinkedBlockingDeque 核心特性
    • LinkedBlockingDeque 核心操作方法逻辑
    • LinkedBlockingDeque 总结
    • LinkedBlockingDeque 使用样例代码
      • 1.基础双端操作示例
      • 2. 生产者-消费者模型(不带超时的阻塞)
      • 3.工作窃取(Work Stealing)模式
      • 4. 带超时的阻塞操作
      • 5. 批量操作与状态监控

BlockingDeque = Deque + BlockingQueue:支持双端操作的线程安全阻塞队列

public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> {}

继承了阻塞队列BlockingQueue 和 双端队列Deque,所以它结合了双端队列(Deque)和阻塞队列(BlockingQueue)的特性

public interface BlockingQueue<E> extends Queue<E> {}
public interface Deque<E> extends Queue<E> {}

BlockingDeque 核心特性

1.线程安全
所有操作都是原子性的,支持多线程并发访问

2.阻塞操作
当队列满时:插入操作会阻塞线程,直到空间可用
当队列空时:移除操作会阻塞线程,直到元素出现

3.双端操作
支持在队列的头部和尾部进行插入/移除操作

4.超时控制
提供带超时参数的 offer 和 poll 方法(避免无限期阻塞)

5.容量限制
可以是有界(固定容量)或无界(默认 Integer.MAX_VALUE)

BlockingDeque 核心方法

BlockingDeque 在 Deque 基础上扩展了阻塞方法

操作 头部 (First) 尾部 (Last)
阻塞插入 putFirst(e) putLast(e)
阻塞移除 takeFirst() takeLast()
超时插入 offerFirst(e, timeout, unit) offerLast(e, timeout, unit)
超时移除 pollFirst(timeout, unit) pollLast(timeout, unit)
继承自 Deque addFirst(e), removeFirst() addLast(e), removeLast()
继承自 Queue put(e) (等价于 putLast(e)) take() (等价于 takeFirst())

注意:

  • putXxx() 在队列满时无限阻塞
  • takeXxx() 在队列空时无限阻塞
  • offerXxx(timeout)pollXxx(timeout) 在超时后返回 false/null

唯一标准实现:LinkedBlockingDeque

LinkedBlockingDeque是 Java 标准库中 BlockingDeque 的唯一实现类

public class LinkedBlockingDeque<E>
    extends AbstractQueue<E>
    implements BlockingDeque<E>, java.io.Serializable {}

继承自AbstractQueue 实现 BlockingDeque 而

public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> {}
public interface BlockingQueue<E> extends Queue<E> {}

public interface Deque<E> extends Queue<E> {}
衍生进程 :
基本队列 衍生出---》 双端队列 和阻塞基本队列(安全) 
双端队列 和阻塞基本队列(安全)   ---》衍生出 阻塞双端队列BlockingDeque
BlockingDeque 唯一实现类 LinkedBlockingDeque

LinkedBlockingDeque 构造方法

1.无参构造方法
创建一个容量为Integer.MAX_VALUE的LinkedBlockingDeque

   public LinkedBlockingDeque() {this(Integer.MAX_VALUE);}

2.指定容量的构造方法
创建一个具有给定容量的LinkedBlockingDeque

public LinkedBlockingDeque(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
}

3.从集合构造
创建一个包含指定集合元素的LinkedBlockingDeque,容量为Integer.MAX_VALUE,并按照集合迭代器的顺序添加元素

   public LinkedBlockingDeque(Collection<? extends E> c) {
       this(Integer.MAX_VALUE);
       // 将集合中的元素逐个添加到队列中
       // 如果添加过程中超过了容量,会抛出IllegalStateException
       //因为容量是MAX_VALUE,所以几乎不会发生,除非集合元素数量超过MAX_VALUE
       for (E e : c)
           add(e);
   }

另外需要注意:
如果集合c为null,会抛出NullPointerException;如果集合c中包含null元素,也会抛出NullPointerException(因为LinkedBlockingDeque不允许null元素)

重要点
1.无界队列(无参构造)可能会因为元素过多而导致内存溢出,所以使用时要注意。
2.有界队列(指定容量)在队列满时,再添加元素的操作会被阻塞(如果使用阻塞方法如put),或者返回特殊值(如offer返回false)。
3.不允许null元素 不允许包含null的集合,否则会抛出NullPointerException
4.迭代器是弱一致性的,即迭代器创建后,队列的修改可能不会在迭代器中反映出来,或者只反映部分修改

LinkedBlockingDeque 数据结构及管理逻辑

Node节点

    static final class Node<E> {
    	E item;         // 存储元素
    	Node<E> prev;   // 前驱节点指针
    	Node<E> next;   // 后继节点指针
        Node(E x) {
            item = x;
        }
    }

关键成员变量

transient Node<E> first;   // 队首节点
transient Node<E> last;    // 队尾节点
private final int capacity; // 队列容量(无界时为 Integer.MAX_VALUE)
private final AtomicInteger count = new AtomicInteger(); // 当前元素数量
private final ReentrantLock lock = new ReentrantLock(); // 全局锁
private final Condition notEmpty = lock.newCondition(); // 非空条件
private final Condition notFull = lock.newCondition();  // 非满条件

并发控制逻辑
1. 锁机制

  • 单锁设计:所有操作共用同一把 ReentrantLock
  • 条件变量
    • notEmpty:队列空时阻塞消费者线程(take/poll)。
    • notFull:队列满时阻塞生产者线程(put/offer)。

2. 阻塞/唤醒流程

插入元素(如 putLast(e)

1. 获取锁
2. 若队列满 → 阻塞在 notFull 条件
3. 插入成功后:- 若原队列空 → 唤醒 notEmpty 上的消费者
4. 释放锁

移除元素(如 takeFirst()

1. 获取锁
2. 若队列空 → 阻塞在 notEmpty 条件
3. 移除成功后:- 若原队列满 → 唤醒 notFull 上的生产者
4. 释放锁

唤醒机制流程图

原队列空
原队列满
put操作成功
唤醒notEmpty等待线程
take操作成功
唤醒notFull等待线程

LinkedBlockingDeque 核心特性

1.双端操作能力

  • 双向操作支持
    • 同时支持队首(addFirst()/removeFirst())和队尾(addLast()/removeLast())的插入/移除操作
    • 提供完整双端队列接口:offerFirst(), pollLast(), peekFirst()
  • 应用场景
  deque.putFirst("紧急任务");  // 优先处理高优先级任务
  deque.takeLast();          // 处理普通任务

2.线程安全与并发控制

机制 实现方式 效果
全局锁 单把 ReentrantLock 简化死锁避免,保证操作原子性
分离条件变量 notEmpty + notFullCondition 精准唤醒生产者/消费者线程
原子计数器 AtomicInteger count 线程安全的元素计数(O(1)复杂度)

锁公平性:默认非公平锁(更高吞吐),可通过构造参数启用公平锁

  new LinkedBlockingDeque<>(100, true);  // 公平锁模式

3.阻塞行为
阻塞策略

方法类型 队列满时 队列空时
阻塞型 put(e) 阻塞 take() 阻塞
超时型 offer(e, timeout) poll(timeout)
立即返回型 offer(e) 返回false poll() 返回null
异常型 add(e) 抛异常 remove() 抛异常

唤醒机制

原队列空
原队列满
put操作成功
唤醒notEmpty等待线程
take操作成功
唤醒notFull等待线程

4.容量灵活性
三种容量模式
1.无界队列(默认):Integer.MAX_VALUE
2.固定有界:构造时指定容量(如 new LinkedBlockingDeque<>(200))
3.动态有界:通过继承实现动态扩容(需重写相关方法)

资源控制示例

  // 限制任务队列防止内存溢出
  LinkedBlockingDeque<Runnable> taskQueue = new LinkedBlockingDeque<>(1000);
  executor = new ThreadPoolExecutor(4, 4, 0, TimeUnit.SECONDS, taskQueue);

5.内存与性能特性

操作 时间复杂度
插入/移除 O(1)
检查元素存在性 O(n)
size() O(1)

6.迭代器特性
弱一致性迭代器

  • 不抛出 ConcurrentModificationException
  • 反映创建迭代器时的队列快照
  • 可能包含创建后的修改(但不保证)

安全遍历示例

  List<String> snapshot = new ArrayList<>();
  lock.lock();  // 手动加锁获取一致性快照
  try {
      for (String s : deque) snapshot.add(s);
  } finally {
      lock.unlock();
  }

7.特殊行为约束

  • Null 禁止:所有方法禁止插入 null 值(抛出 NullPointerException
  • 内存一致性:遵循 java.util.concurrent 包的内存可见性规则
  • 批量操作drainTo() 方法提供高效元素转移
  List<String> list = new ArrayList<>();
  deque.drainTo(list, 100);  // 一次性转移100个元素

LinkedBlockingDeque 核心操作方法逻辑

1.插入操作(以队尾插入为例)

public void putLast(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<>(e); // 创建新节点
    lock.lock();
    try {
        while (!linkLast(node)) { // 尝试插入队尾
            notFull.await();      // 队列满则阻塞
        }
    } finally {
        lock.unlock();
    }
}

private boolean linkLast(Node<E> node) {
    if (count.get() >= capacity) return false; // 队列满,插入失败
    
    Node<E> l = last;
    node.prev = l;
    last = node;
    if (first == null) {  // 原队列为空
        first = node;     // 初始化队首
    } else {
        l.next = node;    // 链接到原队尾
    }
    
    count.getAndIncrement();      // 元素计数+1
    notEmpty.signal();            // 唤醒等待的消费者
    return true;
}

2. 移除操作(以队首移除为例)

public E takeFirst() throws InterruptedException {
    lock.lock();
    try {
        E x;
        while ((x = unlinkFirst()) == null) { // 尝试移除队首
            notEmpty.await();                // 队列空则阻塞
        }
        return x;
    } finally {
        lock.unlock();
    }
}

private E unlinkFirst() {
    Node<E> f = first;
    if (f == null) return null;   // 队列为空
    
    E item = f.item;
    Node<E> next = f.next;
    f.item = null;  // 断开引用,帮助GC
    first = next;
    if (next == null) {       // 移除后队列变空
        last = null;          // 重置队尾
    } else {
        next.prev = null;     // 断开原队首链接
    }
    
    count.getAndDecrement();  // 元素计数-1
    notFull.signal();         // 唤醒等待的生产者
    return item;
}

LinkedBlockingDeque 总结

特性 实现方式 优势 局限性
线程安全 全局锁 (ReentrantLock) 实现简单,避免死锁 吞吐量低于分段锁
容量控制 count 原子计数器 + capacity 上限 精确阻塞/唤醒 无界队列可能内存溢出
双向操作 双向链表 (prev/next 指针) 支持队首/队尾操作 内存开销略高于单向队列
阻塞/唤醒 Condition 条件变量 (notFull/notEmpty) 精准通知,避免无效唤醒 依赖锁机制
空/满判断 count == 0count == capacity 时间复杂度 O(1) -

LinkedBlockingDeque 通过双向链表 + 全局锁 + 条件等待实现了线程安全的双端阻塞队列

适用场景:需要高效双端操作的并发场景(如工作窃取、任务调度)

注意事项
1.无界队列需谨慎使用(可能内存溢出)
2.超高并发场景考虑 ConcurrentLinkedDeque(无锁但非阻塞)

性能关键点
1.锁竞争
所有操作共用一把锁 → 高并发场景可能成为瓶颈
优化建议:若只需单向队列,优先选 LinkedBlockingQueue(分离读/写锁)
2.链表维护
插入/删除需修改相邻节点指针 → 时间复杂度 O(1)
对比数组队列 (ArrayBlockingQueue):避免扩容但内存不连续
3.内存占用
每个元素需额外存储两个节点指针 → 空间开销增大

设计权衡与适用场景

场景 优势 注意事项
工作窃取(Work Stealing) 天然支持双端操作 优于 ConcurrentLinkedDeque 的阻塞特性
生产者-消费者模型 完备的阻塞机制 单锁可能限制吞吐量
高优先级任务处理 putFirst() 支持紧急任务插队 需防止任务饥饿
有限资源池 容量控制防止资源耗尽 合理设置队列大小

与同类容器对比

特性 LinkedBlockingDeque ArrayBlockingQueue ConcurrentLinkedDeque
数据结构 双向链表 环形数组 CAS无锁链表
阻塞支持
双端操作
内存连续性
锁机制 单锁 单锁 无锁
高并发性能 中等 中等

LinkedBlockingDeque 使用样例代码

1.基础双端操作示例

public class BasicOperationsDemo {
    public static void main(String[] args) {
        // 创建容量为3的有界双端队列
        LinkedBlockingDeque<String> deque = new LinkedBlockingDeque<>(3);
        // 在队尾添加元素
        deque.addLast("Task1");
        deque.offerLast("Task2"); // 推荐使用非阻塞方法
        // 在队首添加元素(高优先级)
        deque.addFirst("UrgentTask");
        System.out.println("当前队列: " + deque); // [UrgentTask, Task1, Task2]
        
        // 从队首移除元素
        String firstTask = deque.removeFirst();
        System.out.println("处理任务: " + firstTask); // 处理任务: UrgentTask
        
        // 从队尾移除元素
        String lastTask = deque.pollLast();
        System.out.println("处理任务: " + lastTask); // 处理任务: Task2
        System.out.println("剩余队列: " + deque); // [Task1]
    }
}

2. 生产者-消费者模型(不带超时的阻塞)

public class ProducerConsumerDemo {
    public static void main(String[] args) throws InterruptedException {
        final int CAPACITY = 5;
        LinkedBlockingDeque<Integer> taskQueue = 
        		new LinkedBlockingDeque<>(CAPACITY);
        AtomicInteger counter = new AtomicInteger(0);
        
        // 生产者线程
        Runnable producer = () -> {
            try {
                while (true) {
                    int taskId = counter.incrementAndGet();
                    
                    // 80%概率添加到队尾,20%概率添加到队首(高优先级)
                    if (Math.random() < 0.8) {
                        taskQueue.putLast(taskId);
                        System.out.println("生产普通任务: " + taskId);
                    } else {
                        taskQueue.putFirst(taskId);
                        System.out.println("生产高优先级任务: " + taskId + " ★");
                    }
                    
                    TimeUnit.MILLISECONDS.sleep(100); // 模拟生产耗时
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        };
        
        // 消费者线程
        Runnable consumer = () -> {
            try {
                while (true) {
                    // 从队首获取任务(优先处理高优先级)
                    Integer task = taskQueue.takeFirst();
                    System.out.println("消费任务: " + task + " | 剩余: " + taskQueue.size());
                    
                    TimeUnit.MILLISECONDS.sleep(200); // 模拟处理耗时
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        };
        
        // 启动线程
        ExecutorService executor = Executors.newFixedThreadPool(3);
        executor.submit(producer);
        executor.submit(producer); // 两个生产者
        executor.submit(consumer);
        
        // 运行10秒后关闭
        TimeUnit.SECONDS.sleep(10);
        executor.shutdownNow();
    }
}

3.工作窃取(Work Stealing)模式

public class WorkStealingDemo {
    public static void main(String[] args) {
        final int WORKER_COUNT = 3;
        // queues: 一个LinkedBlockingDeque数组,大小为WORKER_COUNT(即3)
        LinkedBlockingDeque<Runnable>[] queues = new LinkedBlockingDeque[WORKER_COUNT];
        // workers: 一个ExecutorService数组,大小也为WORKER_COUNT
        ExecutorService[] workers = new ExecutorService[WORKER_COUNT];
        
        for (int i = 0; i < WORKER_COUNT; i++) {
        	//初始化每个队列 分布设置成一个容量为10的LinkedBlockingDeque任务队列
            queues[i] = new LinkedBlockingDeque<>(10);
            final int workerId = i;
            //每个线程使用 Executors.newSingleThreadExecutor 创建 单线程执行器
            // 每个workerId对应queues[i] 及每个线程中一个容量为10的的队列
            workers[i] = Executors.newSingleThreadExecutor(r -> 
                new Thread(r, "Worker-" + workerId));
        }
        
        // 提交初始任务 创建20个简单任务(打印当前线程名)
        for (int i = 0; i < 20; i++) {
        	//任务被均匀分配到3个队列中(使用取模运算 i % WORKER_COUNT)
            int queueIdx = i % WORKER_COUNT;
            queues[queueIdx].offer(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行任务");
            });
        }
        
        // 工作线程逻辑
        for (int i = 0; i < WORKER_COUNT; i++) {
            final int workerIndex = i;
            workers[i].execute(() -> {
                try {
                	//优先处理自己的任务
                    while (!Thread.currentThread().isInterrupted()) {
                        Runnable task = queues[workerIndex].pollFirst
                        				(100, TimeUnit.MILLISECONDS);
                        
                        if (task != null) {
                            task.run();
                        } else {
                            // 工作窃取:从其他队列尾部偷取任务
                            for (int j = 0; j < WORKER_COUNT; j++) {
                                if (j != workerIndex) {
                                    task = queues[j].pollLast();
                                    if (task != null) {
System.out.println(Thread.currentThread().getName() + " 窃取任务");
                                        task.run();
                                        break;// 窃取一个任务后立即跳出
                                    }
                                }
                            }
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
        
        // 运行5秒后关闭
        try {
            TimeUnit.SECONDS.sleep(5);
            for (ExecutorService worker : workers) {
                worker.shutdownNow();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

负载均衡策略

任务
任务
任务
窃取
窃取
窃取
窃取
窃取
窃取
Worker-0
队列0
Worker-1
队列1
Worker-2
队列2

4. 带超时的阻塞操作

public class TimeoutOperationsDemo {
    public static void main(String[] args) {
        LinkedBlockingDeque<String> messageQueue = new LinkedBlockingDeque<>(2);
        
        // 生产者线程(快速生产)
        new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    String msg = "Message-" + i;
                    
                    // 带超时的插入操作
                    if (messageQueue.offerLast(msg, 500, TimeUnit.MILLISECONDS)) {
                        System.out.println("成功发送: " + msg);
                    } else {
                        System.out.println("发送超时: " + msg + " (队列已满)");
                    }
                    TimeUnit.MILLISECONDS.sleep(300);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
        
        // 消费者线程(慢速消费)
        new Thread(() -> {
            try {
                while (true) {
                    // 带超时的获取操作
                    String msg = messageQueue.pollFirst(1, TimeUnit.SECONDS);
                    if (msg == null) {
                        System.out.println("等待超时,无新消息");
                        break;
                    }
                    System.out.println("处理消息: " + msg);
                    TimeUnit.MILLISECONDS.sleep(800);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}

5. 批量操作与状态监控

public class BatchOperationsDemo {
    public static void main(String[] args) throws InterruptedException {
        LinkedBlockingDeque<Integer> dataQueue = new LinkedBlockingDeque<>(100);
        
        // 生产者填充数据
        for (int i = 1; i <= 50; i++) {
            dataQueue.put(i);
        }
        
        // 批量转移数据(最多转移20个元素)
        List<Integer> batch = new ArrayList<>();
        int transferred = dataQueue.drainTo(batch, 20);
        System.out.println("转移了 " + transferred + " 个元素: " + batch);
        
        // 获取但不移除元素
        Integer first = dataQueue.peekFirst();
        Integer last = dataQueue.peekLast();
        System.out.println("队首: " + first + ", 队尾: " + last);
        
        // 检查元素存在性
        boolean contains25 = dataQueue.contains(25);
        System.out.println("队列包含25? " + contains25);
        
        // 获取队列状态
        System.out.println("当前大小: " + dataQueue.size());
        System.out.println("剩余容量: " + dataQueue.remainingCapacity());
        
        // 清空队列
        dataQueue.clear();
        System.out.println("清空后大小: " + dataQueue.size());
    }
}

LinkedBlockingDeque 通过平衡 线程安全双端灵活性阻塞语义,成为 Java 并发库中实现双端阻塞队列的标准解决方案。其设计在大多数并发场景下表现出良好的稳定性和可预测性。

你可能感兴趣的:(Java基础 集合框架 队列架构 阻塞双端队列BlockingDeque架构)