Java并发基础:LinkedBlockingDeque全面解析!

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: LinkedBlockingDeque提供了线程安全的双端队列实现,它支持在队列两端高效地进行插入和移除操作,同时具备阻塞功能,能够很好地协调生产者与消费者之间的速度差异,其内部基于链表结构,使得并发性能优异,是处理多线程间数据传递的理想选择。

Java并发基础:LinkedBlockingDeque全面解析! - 程序员古德

内容概要

LinkedBlockingDeque提供了线程安全的双端队列实现,它支持在队列两端高效地进行插入和移除操作,同时具备阻塞功能,能够很好地协调生产者与消费者之间的速度差异,其内部基于链表结构,使得并发性能优异,是处理多线程间数据传递的理想选择。

核心概念

LinkedBlockingDeque 实现了一个线程安全的双端队列(Deque,即 double-ended queue),这个队列在两端都可以添加和移除元素,而且它是阻塞的,意味着当队列为空时,如果线程尝试从队列中取元素,线程会被阻塞,直到队列中有元素可供取出;同样地,如果队列已满,尝试添加元素的线程也会被阻塞,直到队列中有空间可供添加新元素。

举一个生活中的实际案例,比如一个面包店,面包师傅负责生产面包(生产者),顾客来店里买面包(消费者),面包师傅做好面包后,会把它们放在一个展示架上供顾客挑选;顾客则从这个展示架上取走他们想要的面包,这里使用LinkedBlockingDeque 来模拟这个场景。面包师傅(生产者线程)在队列的一端放入新做好的面包(添加元素到队列),而顾客(消费者线程)从队列的另一端取走面包(从队列中移除元素):

  1. 阻塞特性:如果展示架上没有面包(队列为空),顾客就会被阻塞,直到面包师傅做好新的面包并放到展示架上,同样,如果展示架满了(队列已满),面包师傅就会被阻塞,直到有顾客取走一些面包,腾出空间来放新的面包。
  2. 双端操作:在这个场景中,虽然通常面包师傅只在一端放面包,顾客在另一端取面包,但双端队列的灵活性意味着也可以轻松改变这个行为,比如,如果有特殊情况,面包师傅可以从展示架上取回一些面包(从队列的另一端移除元素),或者顾客可以预先把他们的面包订单放到展示架上(在队列的另一端添加元素)。

LinkedBlockingDeque 是一个线程安全的双端队列,允许从队列的两端添加和移除元素,并且它是阻塞的,他通常用来解决以下问题:

  1. 线程安全:在多线程环境中,当多个线程需要访问和修改共享数据时,LinkedBlockingDeque 提供了一种线程安全的方式来存储和检索这些数据,它内部的同步机制确保了数据的一致性和完整性。
  2. 阻塞操作:当队列为空时,消费者线程调用 take() 方法会被阻塞,直到生产者线程向队列中添加元素,同样,当队列已满时,生产者线程调用 put() 方法也会被阻塞,直到消费者线程从队列中移除元素,这种阻塞行为有助于防止线程在不必要的情况下空转或浪费CPU资源。
  3. 容量限制LinkedBlockingDeque 可以在创建时指定一个最大容量,这个容量限制了队列中可以存储的元素数量,有助于防止内存溢出,当队列达到最大容量时,生产者线程会被阻塞,直到队列中有空间可用。
  4. 双端操作:与普通的 BlockingQueue 接口实现相比,LinkedBlockingDeque 提供了双端队列的功能,允许从队列的两端添加和移除元素,这为某些特定的应用场景提供了更大的灵活性。
  5. 高效的并发性能:由于其内部使用链表数据结构,LinkedBlockingDeque 在处理大量并发操作时通常具有较好的性能,它适用于需要高吞吐量和低延迟的生产者-消费者场景。

代码案例

下面是一个简单的例子,演示了如何使用 LinkedBlockingDeque 类,如下代码:

import java.util.concurrent.BlockingQueue;  
import java.util.concurrent.LinkedBlockingDeque;  

public class LinkedBlockingDequeExample {
   
     

    public static void main(String[] args) throws InterruptedException {
   
     
        // 创建一个容量为 5 的 LinkedBlockingDeque  
        BlockingQueue<String> deque = new LinkedBlockingDeque<>(5);  

        // 启动一个生产者线程,向队列中添加元素  
        Thread producer = new Thread(() -> {
   
     
            try {
   
     
                for (int i = 0; i < 10; i++) {
   
     
                    String item = "Item-" + i;  
                    deque.put(item); // 当队列满时,该方法会阻塞  
                    System.out.println("Produced: " + item);  
                    Thread.sleep(200); // 模拟生产延迟  
                }  
            } catch (InterruptedException e) {
   
     
                e.printStackTrace();  
            }  
        });  

        // 启动一个消费者线程,从队列中移除元素  
        Thread consumer = new Thread(() -> {
   
     
            try {
   
     
                for (int i = 0; i < 10; i++) {
   
     
                    String item = deque.take(); // 当队列空时,该方法会阻塞  
                    System.out.println("Consumed: " + item);  
                    Thread.sleep(300); // 模拟消费延迟  
                }  
            } catch (InterruptedException e) {
   
     
                e.printStackTrace();  
            }  
        });  

        // 启动生产者和消费者线程  
        producer.start();  
        consumer.start();  

        // 等待两个线程执行完成  
        producer.join();  
        consumer.join();  

        System.out.println("Producer and Consumer threads have finished.");  
    }  
}

在上面代码中,创建了一个 LinkedBlockingDeque 实例,并指定了它的最大容量为 5,然后创建了一个生产者线程和一个消费者线程,生产者线程将循环 10 次,每次生产一个字符串并将其放入队列中,如果队列已满,put 方法将会阻塞直到队列中有空间可用,消费者线程也将循环 10 次,每次从队列中取出一个元素并打印它,如果队列为空,take 方法将会阻塞直到队列中有元素可取。

由于生产者和消费者线程的速度可能不同,LinkedBlockingDeque 作为一个阻塞队列,能够协调这两个线程之间的速度差异,确保它们可以协同工作,而不会因为队列为空或满而导致任何一方停滞不前。

核心API

LinkedBlockingDeque 实现了 BlockingDeque 接口,是一个线程安全的双端队列,以下是 LinkedBlockingDeque 类中一些主要方法的含义:

  1. add(E e)
    将指定的元素插入到此双端队列表示的队列中(即在此双端队列的尾部),如果立即可行且不会违反容量限制,则成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException,这是 Queue 接口的方法。
  2. offer(E e)
    将指定的元素插入到此双端队列表示的队列中(即在此双端队列的尾部),如果立即可行且不会违反容量限制,则成功时返回 true,如果当前没有可用的空间,则返回 false,这是 Queue 接口的方法。
  3. put(E e) throws InterruptedException
    将指定的元素插入到此双端队列表示的队列中(即在此双端队列的尾部),必要时将等待空间变得可用,这是 BlockingQueue 接口的方法。
  4. offer(E e, long timeout, TimeUnit unit)
    将指定的元素插入到此双端队列表示的队列中,必要时将等待指定的时间以使空间变得可用,这是 BlockingQueue 接口的方法。
  5. remove()
    获取并移除此双端队列表示的队列的头部,如果此双端队列为空,则抛出 NoSuchElementException,这是 Queue 接口的方法。
  6. poll()
    获取并移除此双端队列表示的队列的头部,如果此双端队列为空,则返回 null,这是 Queue 接口的方法。
  7. take() throws InterruptedException
    获取并移除此双端队列表示的队列的头部,在元素变得可用之前一直等待,这是 BlockingQueue 接口的方法。
  8. poll(long timeout, TimeUnit unit)
    获取并移除此双端队列表示的队列的头部,在指定的时间内等待元素变得可用,这是 BlockingQueue 接口的方法。
  9. peek()
    获取但不移除此双端队列表示的队列的头部,如果此双端队列为空,则返回 null,这是 Queue 接口的方法。
  10. element()
    获取但不移除此双端队列表示的队列的头部,这是 Queue 接口的方法。
  11. push(E e)
    将元素推入此双端队列表示的堆栈中(即在此双端队列的头部),如果立即可行且不会违反容量限制,则成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException
  12. pop()
    从此双端队列表示的堆栈中弹出一个元素,如果此双端队列为空,则抛出 NoSuchElementException
  13. addFirst(E e), addLast(E e)
    将指定的元素插入此双端队列的开头或结尾。
  14. offerFirst(E e), offerLast(E e)
    将指定的元素插入此双端队列的开头或结尾,如果立即可行且不会违反容量限制,则成功时返回 true,如果当前没有可用的空间,则返回 false
  15. removeFirst(), removeLast()
    获取并移除此双端队列的第一个元素或最后一个元素。
  16. pollFirst(), pollLast()
    获取并移除此双端队列的第一个元素或最后一个元素,如果此双端队列为空,则返回 null
  17. getFirst(), getLast()
    获取但不移除此双端队列的第一个元素或最后一个元素。
  18. peekFirst(), peekLast()
    获取但不移除此双端队列的第一个元素或最后一个元素,如果此双端队列为空,则返回 null

核心总结

Java并发基础:LinkedBlockingDeque全面解析! - 程序员古德

LinkedBlockingDeque类它融合了阻塞队列和双端队列的特性,其优点在于高效的并发性能和灵活的两端操作,适合在生产者-消费者场景中使用,能够很好地处理多线程间的数据共享和传递,缺点在高并发下如果队列大小设置不当,可能会导致过多的线程阻塞,影响系统整体性能,此外,由于是基于链表的实现,其内存占用可能相对较高。

关注我,每天学习互联网编程技术 - 程序员古德

END!

往期回顾

Java并发基础:LinkedBlockingDeque全面解析!

Java并发基础:LinkedTransferQueue全面解析!

Java并发基础:LinkedBlockingQueue全面解析!

Java并发基础:Deque接口和Queue接口的区别?

Spring核心基础:全面总结Spring中提供的那些基础工具类!

相关文章
|
6天前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
19 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
9天前
|
监控 算法 Java
深入解析Java中的垃圾回收机制
本文旨在全面解析Java的垃圾回收机制,探讨其工作原理、常见算法以及在实际开发中的应用。通过对这一重要主题的深入分析,希望帮助读者更好地理解Java虚拟机(JVM)如何管理内存,从而编写出更高效、稳定的Java应用程序。
|
10天前
|
Java 开发者
Java中的异常处理机制深度解析
在Java编程中,异常处理是保证程序稳定性和健壮性的重要手段。本文将深入探讨Java的异常处理机制,包括异常的分类、捕获与处理、自定义异常以及一些最佳实践。通过详细讲解和代码示例,帮助读者更好地理解和应用这一机制,提升代码质量。
12 1
|
11天前
|
分布式计算 Java API
深入解析Java中的Lambda表达式及其应用
本文将深入探讨Java中Lambda表达式的定义、优势及其在实际编程中的应用。通过具体示例,帮助读者更好地理解和使用这一强大的编程工具。
|
2月前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
2月前
|
存储 NoSQL Redis
redis 6源码解析之 object
redis 6源码解析之 object
58 6
|
21天前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
26天前
|
开发工具
Flutter-AnimatedWidget组件源码解析
Flutter-AnimatedWidget组件源码解析
|
21天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
178 37
|
13天前
|
编解码 开发工具 UED
QT Widgets模块源码解析与实践
【9月更文挑战第20天】Qt Widgets 模块是 Qt 开发中至关重要的部分,提供了丰富的 GUI 组件,如按钮、文本框等,并支持布局管理、事件处理和窗口管理。这些组件基于信号与槽机制,实现灵活交互。通过对源码的解析及实践应用,可深入了解其类结构、布局管理和事件处理机制,掌握创建复杂 UI 界面的方法,提升开发效率和用户体验。
64 12

推荐镜像

更多
下一篇
无影云桌面