Java中的LinkedBlockingQueue详解

Java中的LinkedBlockingQueue详解

一、概述

LinkedBlockingQueue 是 Java 并发包(java.util.concurrent)中基于链表实现的阻塞队列,支持可选容量限制(默认无界),通过 双锁机制ReentrantLock)和 条件变量Condition)实现线程安全。其核心设计目标是高吞吐量生产者-消费者模型的高效协作

关键特性

  1. 线程安全:通过分离的 putLock(入队锁)和 takeLock(出队锁)实现并发控制,减少锁竞争。
  2. 阻塞操作put()take() 方法在队列满或空时自动阻塞,支持超时等待。
  3. FIFO 顺序:严格遵循先进先出原则。
  4. 可选容量:构造时可指定最大容量(有界),默认无界(Integer.MAX_VALUE)。
  5. 弱一致性迭代器:遍历时可能看到部分更新,但不会抛出 ConcurrentModificationException

二、内部数据结构

1. 节点类 Node<E>

staticclassNode<E>{E item;// 存储元素Node<E> next;// 指向下一个节点Node(E x){ item = x;}}
  • 链表结构:通过 head(头节点)和 last(尾节点)维护队列顺序。
  • 哨兵节点:初始化时 headlast 均指向一个 item=null 的哨兵节点,简化边界处理。

2. 核心属性

属性作用
capacity队列最大容量(默认 Integer.MAX_VALUE
count当前元素数量(AtomicInteger 保证原子性)
putLock/takeLock入队和出队操作的独占锁
notEmpty/notFull条件变量,分别用于队列空和队列满时的线程阻塞与唤醒

三、核心方法与实现原理

1. 入队操作

put(E e)
publicvoidput(E e)throwsInterruptedException{if(e ==null)thrownewNullPointerException();int c =-1;Node<E> node =newNode<>(e);finalReentrantLock putLock =this.putLock;finalAtomicInteger count =this.count; putLock.lockInterruptibly();// 获取入队锁try{while(count.get()== capacity){ notFull.await();// 队列满时阻塞}enqueue(node);// 入队操作 c = count.getAndIncrement();if(c +1< capacity) notFull.signal();// 唤醒其他生产者线程}finally{ putLock.unlock();}if(c ==0)signalNotEmpty();// 若队列之前为空,唤醒消费者线程}
  • 阻塞逻辑:队列满时调用 notFull.await(),释放锁并等待。
  • 唤醒机制:入队成功后,若队列未满,通过 notFull.signal() 唤醒其他生产者线程。
offer(E e)
publicbooleanoffer(E e){if(e ==null)thrownewNullPointerException();finalAtomicInteger count =this.count;if(count.get()== capacity)returnfalse;// 非阻塞,直接返回失败int c =-1;Node<E> node =newNode<>(e);finalReentrantLock putLock =this.putLock; putLock.lock();try{if(count.get()< capacity){enqueue(node); c = count.getAndIncrement();if(c +1< capacity) notFull.signal();}}finally{ putLock.unlock();}if(c ==0)signalNotEmpty();return c >=0;}
  • 非阻塞特性:队列满时立即返回 false,不阻塞线程。

2. 出队操作

take()
publicEtake()throwsInterruptedException{E x;int c =-1;finalAtomicInteger count =this.count;finalReentrantLock takeLock =this.takeLock; takeLock.lockInterruptibly();// 获取出队锁try{while(count.get()==0){ notEmpty.await();// 队列空时阻塞} x =dequeue();// 出队操作 c = count.getAndDecrement();if(c >1) notEmpty.signal();// 唤醒其他消费者线程}finally{ takeLock.unlock();}if(c == capacity)signalNotFull();// 若队列之前满,唤醒生产者线程return x;}
  • 阻塞逻辑:队列空时调用 notEmpty.await(),释放锁并等待。
  • 唤醒机制:出队成功后,若队列非空,通过 notEmpty.signal() 唤醒其他消费者线程。
poll()
publicEpoll(){finalAtomicInteger count =this.count;if(count.get()==0)returnnull;// 非阻塞,直接返回 nullE x =null;int c =-1;finalReentrantLock takeLock =this.takeLock; takeLock.lock();try{if(count.get()>0){ x =dequeue(); c = count.getAndDecrement();if(c >1) notEmpty.signal();}}finally{ takeLock.unlock();}if(c == capacity)signalNotFull();return x;}
  • 非阻塞特性:队列空时立即返回 null,不阻塞线程。

四、线程安全与锁机制

1. 双锁分离

  • putLock:控制入队操作,允许多个生产者并发插入。
  • takeLock:控制出队操作,允许多个消费者并发移除。
  • 优势:减少锁竞争,提升吞吐量(相比单锁的 ArrayBlockingQueue)。

2. 条件变量

  • notEmpty:队列空时,消费者线程在此条件上等待。
  • notFull:队列满时,生产者线程在此条件上等待。
  • 唤醒策略:插入或移除元素后,通过 signal()signalAll() 唤醒等待线程。

五、适用场景

  1. 生产者-消费者模型:平衡生产者和消费者的处理速度,避免资源浪费。
  2. 线程池任务队列:如 ThreadPoolExecutor 默认使用 LinkedBlockingQueue 作为任务缓冲区。
  3. 数据流水线处理:在多阶段数据处理中传递数据。
  4. 限流场景:通过设置容量限制防止系统过载。

六、与其他队列的对比

特性LinkedBlockingQueueArrayBlockingQueueConcurrentLinkedQueue
线程安全机制双锁分离单锁(ReentrantLock)CAS + 自旋
容量可选有界(默认无界)必须有界无界
性能高并发下吞吐量高低并发下延迟低读多写少场景更优
适用场景生产者-消费者、任务队列固定容量缓冲区高并发无锁队列

七、注意事项

  1. 内存风险:默认无界队列可能导致内存溢出(OOM),建议生产环境指定容量。
  2. 中断处理:阻塞方法(如 put()/take())会响应中断,需捕获 InterruptedException 并处理中断状态。
  3. 性能调优:高并发场景下,合理设置队列容量和线程池参数(如 corePoolSizemaximumPoolSize)。

八、源码设计细节

  1. 哨兵节点:初始化时 headlast 均指向空节点,简化插入和删除逻辑。
  2. 弱一致性迭代器:遍历时基于当前快照,允许并发修改但可能遗漏新元素。
  3. 内存管理:节点通过 new 分配,依赖 GC 回收,避免手动内存管理开销。

九、总结

LinkedBlockingQueue 是 Java 并发编程中高性能阻塞队列的典范,通过双锁分离和条件变量机制,在保证线程安全的同时最大化吞吐量。适用于生产者-消费者模型、线程池任务队列等场景,但需注意无界队列的内存风险。理解其底层实现(如双锁、条件变量)有助于在实际工程中合理应用。


Java中的ConcurrentLinkedQueue详解
Vert.x 4 学习笔记

Read more

FPGA实现HDMI输出完全攻略:从接口原理到4K显示全流程(附代码模板+调试技巧)

FPGA实现HDMI输出完全攻略:从接口原理到4K显示全流程(附代码模板+调试技巧) 📚 目录导航 文章目录 * FPGA实现HDMI输出完全攻略:从接口原理到4K显示全流程(附代码模板+调试技巧) * 📚 目录导航 * 概述 * 一、HDMI基础概念 * 1.1 HDMI接口介绍 * 1.1.1 HDMI接口历史与发展 * 1.1.2 HDMI接口引脚定义 * 1.1.3 HDMI版本对比 * 1.2 HDMI版本演进 * 1.2.1 HDMI 1.4特性 * 1.2.2 HDMI 2.0特性 * 1.2.3 HDMI 2.1特性

By Ne0inhk
Nano Banana进行AI绘画中文总是糊?一招可重新渲染,清晰到可直接汇报

Nano Banana进行AI绘画中文总是糊?一招可重新渲染,清晰到可直接汇报

文章目录 * 1. 为什么 Nano Banana 生成的中文经常不清晰? * 2. 解决思路:Nano Banana + Seedream 4.5 的两段式工作流 * 3. 实战:先用 Nano Banana 生成架构图(中文会糊) * 4. 部署 Personal LLM API,并配置 Seedream 4.5 * 5. 用 Cherry Studio 配置已部署的 LLM 接口 * 6. 关键一步:用 Seedream 4.5 对“中文文字重新渲染” * 7. 效果对比:字清晰、无错位、图形保持不变

By Ne0inhk

5分钟部署麦橘超然Flux,低显存设备也能玩转AI绘画

5分钟部署麦橘超然Flux,低显存设备也能玩转AI绘画 1. 为什么你值得花5分钟试试这个Flux控制台 你是不是也遇到过这些情况: * 想试试最新的Flux模型,但显卡只有8GB甚至6GB,一加载就报“CUDA out of memory”; * 下载完模型还要手动配置路径、改代码、调参数,折腾两小时还没看到一张图; * 网页版用着方便,但担心隐私泄露、生成被限速、图片被缓存; 别再纠结了——麦橘超然 - Flux 离线图像生成控制台,就是为这类真实场景而生的。它不是又一个需要编译、调参、查文档的实验项目,而是一个开箱即用的本地Web服务:模型已打包进镜像,float8量化技术让DiT主干网络显存占用直降近一半,Gradio界面简洁到连提示词输入框都标好了占位符,连SSH隧道怎么转发都给你写好了命令。 更重要的是,它真的能在你的旧笔记本、远程小内存服务器、甚至实验室里那台只配了RTX 3060的工位机上跑起来。本文不讲原理推导,不堆术语,就带你从零开始,5分钟内完成部署、打开浏览器、输入第一句描述、亲眼看到AI画出赛博朋克雨夜街道——所有操作一步接一步,复制粘贴就能

By Ne0inhk

简单易学的分离式部署小米智能家居Miloco方法

一、安装环境 * Windows用户:安装WSL2以及Docker * macOS/Linux用户:安装Docker 此处不再赘述,网上随便找个教程即可。特别地,对于Windows用户来说,你需要将 WSL2 的网络模式设置为 Mirrored。 二、使用Docker部署Miloco后端 以下均为bash命令。请Windows用户进入WSL2 / Linux、macOS用户进入终端操作: mkdir miloco cd milico vi docker-compose.yml 以下是compose的内容(不会使用vi的同学可以傻瓜式操作:先按i,再使用粘贴功能,然后按冒号,输入wq然后回车,记得关闭输入法): services:backend:container_name: miloco-backend image: ghcr.nju.edu.cn/xiaomi/miloco-backend:latest network_mode:

By Ne0inhk