Java并发基础:BlockingQueue和BlockingDeque接口的区别?

简介: BlockingQueue 和 BlockingDeque 它们都支持在并发编程中的线程安全操作,但是,这两个接口之间存在一些关键的区别,主要在于它们所支持的操作和数据结构的特性,

Java并发基础:BlockingQueue和BlockingDeque接口的区别? - 程序员古德

核心概念

BlockingQueueBlockingDeque 它们都支持在并发编程中的线程安全操作,但是,这两个接口之间存在一些关键的区别,主要在于它们所支持的操作和数据结构的特性,如下:

1、数据结构特性

  1. BlockingQueue 是一个支持线程安全的队列,即它遵循 FIFO(先进先出)原则,可以向队列的尾部添加元素,并从队列的头部移除元素。
  2. BlockingDeque 是一个支持线程安全的双端队列(Deque,也称为双头队列),因此,可以在队列的头部和尾部添加或移除元素,BlockingDeque 提供了比 BlockingQueue 更多的操作灵活性。

2、操作

  1. BlockingQueue 提供了基本的队列操作,如 add(), offer(), put(), take(), poll() 等,这些方法主要用于在队列的尾部添加元素和从队列的头部移除元素。
  2. BlockingDeque 除了提供与 BlockingQueue 类似的操作外(但通常是以不同的名称提供,例如 offerFirst(), offerLast(), takeFirst(), takeLast() 等),还支持在队列的头部进行添加和移除操作的方法,如 offerFirst(), pollFirst(), peekFirst() 等,此外,它还提供了 push(), pop() 等栈操作,因为双端队列可以模拟栈的行为。

3、使用场景

  1. 当需要一个简单的、线程安全的 FIFO 队列时,BlockingQueue 是一个很好的选择,它通常用于生产者-消费者场景,其中生产者将数据放入队列,消费者从队列中取出数据。
  2. 当需要更多的灵活性,例如在队列的头部和尾部都能添加或移除元素时,BlockingDeque 是一个更好的选择,这种数据结构在需要同时维护队列和栈行为的场景中特别有用。

4、实现类

  1. BlockingQueue 提供了多种实现,如 ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue 等。
  2. BlockingDeque,常见的实现是 LinkedBlockingDeque,这个实现提供了一个基于链表的双端队列,支持在队列的两端进行高效的插入和移除操作。

代码案例

Java并发基础:BlockingQueue和BlockingDeque接口的区别? - 程序员古德

BlockingQueue

BlockingQueue 接口表示一个可以存取元素,并且线程安全的队列,换句话说,多个线程可以同时从这个队列中安全地插入或者移除元素,BlockingQueue 的特性在于它支持在队列为空时,获取元素的线程将会等待,直到有元素可获取;当队列已满时,试图插入元素的线程将会等待,直到队列中有可用的空间,它的主要功能如下:

  1. 线程安全BlockingQueue 的所有操作都是线程安全的,这意味着在多线程环境中,可以安全地添加或移除元素,而不需要额外的同步措施。
  2. 阻塞操作:当队列为空时,从队列中获取元素的线程将会被阻塞,直到其他线程向队列中插入元素,同样,当队列已满时,试图插入元素的线程也会被阻塞,直到队列中有空间可用。
  3. 支持限时等待:除了无限期的等待外,BlockingQueue 还提供了带有超时参数的方法,允许线程在指定的时间内等待元素的插入或移除。
  4. 容量可选BlockingQueue 的实现类可以选择有界或无界,有界队列有一个固定的容量,而无界队列的容量则只受限于可用内存。

它有以下使用场景:

  1. 生产者-消费者模式:这是 BlockingQueue 最常见的使用场景,在这种模式中,生产者线程生成数据并将其放入队列,而消费者线程从队列中取出数据并处理,BlockingQueue 简化了生产者-消费者模式的实现,因为它负责处理线程间的同步和通信。
  2. 任务调度BlockingQueue 也可以用于任务调度系统中,在这种情况下,可以将待处理的任务作为元素添加到队列中,然后由工作线程从队列中取出任务并处理。
  3. 缓冲:在需要缓冲数据流或事件流的系统中,BlockingQueue 可以作为缓冲区使用,它允许数据或事件的生产者和消费者以不同的速率运行,而不会丢失数据或造成拥塞。
  4. 线程池:在 ExecutorService 框架中,BlockingQueue 用于存储待执行的任务,线程池中的工作线程从队列中取出任务并执行。

下面是一个简单的生产者-消费者示例,展示了如何使用 BlockingQueue,如下代码:

import java.util.concurrent.BlockingQueue;  
import java.util.concurrent.LinkedBlockingQueue;  

public class ProducerConsumerExample {
   
     

    public static void main(String[] args) {
   
     
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);  

        // 生产者线程  
        Thread producer = new Thread(() -> {
   
     
            for (int i = 0; i < 10; i++) {
   
     
                try {
   
     
                    System.out.println("生产者生产了 " + i);  
                    queue.put(i); // 如果队列满了,将会阻塞  
                    Thread.sleep(200); // 模拟生产时间  
                } catch (InterruptedException e) {
   
     
                    e.printStackTrace();  
                }  
            }  
        });  

        // 消费者线程  
        Thread consumer = new Thread(() -> {
   
     
            for (int i = 0; i < 10; i++) {
   
     
                try {
   
     
                    Integer item = queue.take(); // 如果队列为空,将会阻塞  
                    System.out.println("消费者消费了 " + item);  
                    Thread.sleep(500); // 模拟消费时间  
                } catch (InterruptedException e) {
   
     
                    e.printStackTrace();  
                }  
            }  
        });  

        // 启动线程  
        producer.start();  
        consumer.start();  
    }  
}

BlockingDeque

BlockingDeque结合了BlockingQueue的阻塞特性和Deque(双端队列)的双端操作能力,从而提供了一个线程安全的、支持在两端添加和移除元素的阻塞队列,它有以下主要功能:

  1. 线程安全BlockingDeque的所有操作都是线程安全的,因此多个线程可以安全地同时访问它。
  2. 阻塞操作:如果队列为空,尝试从队列中取出元素的线程将会被阻塞,直到其他线程向队列中插入元素,同样,如果队列已满(对于有界队列),尝试插入元素的线程也会被阻塞,直到队列中有空间可用。
  3. 双端操作:与普通的Deque一样,BlockingDeque支持在队列的两端添加(addFirst, addLast, offerFirst, offerLast)和移除元素(removeFirst, removeLast, pollFirst, pollLast),此外,它还提供了检查队列两端元素的方法(getFirst, getLast, peekFirst, peekLast)。
  4. 容量可选BlockingDeque的实现类可以选择有界或无界,有界队列有一个固定的容量,而无界队列的容量则只受限于可用内存。

它有以下使用场景:

  1. 生产者-消费者模式:这是BlockingDeque最常见的使用场景之一,生产者线程在队列的一端添加元素,而消费者线程在另一端移除元素,这种模式特别适用于需要缓冲数据流或任务队列的系统。
  2. 工作窃取算法:在并行计算中,BlockingDeque可以用作工作窃取队列,实现工作线程之间的任务分配,当一个线程完成了自己的任务时,它可以从其他线程的任务队列中“窃取”任务来执行。
  3. 双端操作的需求:任何需要在队列的两端进行添加和移除操作的并发场景都可以使用BlockingDeque,例如,实现一个并发的LRU(最近最少使用)缓存时,可能需要使用BlockingDeque来维护访问顺序。

下面是一个使用BlockingDeque的简单生产者-消费者示例,如下代码:

import java.util.concurrent.BlockingDeque;  
import java.util.concurrent.LinkedBlockingDeque;  

public class ProducerConsumerWithBlockingDeque {
   
     

    public static void main(String[] args) {
   
     
        BlockingDeque<Integer> deque = new LinkedBlockingDeque<>(5);  

        // 生产者线程  
        Thread producer = new Thread(() -> {
   
     
            for (int i = 0; i < 10; i++) {
   
     
                try {
   
     
                    System.out.println("生产者生产了 " + i);  
                    deque.putFirst(i); // 在队列头部插入元素,如果队列满了,将会阻塞  
                    Thread.sleep(200); // 模拟生产时间  
                } catch (InterruptedException e) {
   
     
                    e.printStackTrace();  
                }  
            }  
        });  

        // 消费者线程  
        Thread consumer = new Thread(() -> {
   
     
            for (int i = 0; i < 10; i++) {
   
     
                try {
   
     
                    Integer item = deque.takeLast(); // 从队列尾部移除元素,如果队列为空,将会阻塞  
                    System.out.println("消费者消费了 " + item);  
                    Thread.sleep(500); // 模拟消费时间  
                } catch (InterruptedException e) {
   
     
                    e.printStackTrace();  
                }  
            }  
        });  

        // 启动线程  
        producer.start();  
        consumer.start();  
    }  
}

在这个示例中,创建了一个容量为5的LinkedBlockingDeque,生产者线程在队列的头部插入整数,而消费者线程在队列的尾部移除整数,注意,当队列满时,生产者线程会被阻塞;同样,当队列空时,消费者线程也会被阻塞,此外,使用putFirsttakeLast方法来演示双端队列的特性,也可以使用putLasttakeFirst来改变插入和移除元素的顺序。

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

END!

往期回顾

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

Java并发基础:BlockingQueue和BlockingDeque接口的区别?

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

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

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

相关文章
|
13天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
4天前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
|
4天前
|
Java
在Java中,接口之间可以继承吗?
接口继承是一种重要的机制,它允许一个接口从另一个或多个接口继承方法和常量。
19 1
|
14天前
|
Java
java线程接口
Thread的构造方法创建对象的时候传入了Runnable接口的对象 ,Runnable接口对象重写run方法相当于指定线程任务,创建线程的时候绑定了该线程对象要干的任务。 Runnable的对象称之为:线程任务对象 不是线程对象 必须要交给Thread线程对象。 通过Thread的构造方法, 就可以把任务对象Runnable,绑定到Thread对象中, 将来执行start方法,就会自动执行Runable实现类对象中的run里面的内容。
30 1
|
18天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
19天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
44 4
|
21天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
23天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
45 2
|
24天前
|
Java
Java基础(13)抽象类、接口
本文介绍了Java面向对象编程中的抽象类和接口两个核心概念。抽象类不能被实例化,通常用于定义子类的通用方法和属性;接口则是完全抽象的类,允许声明一组方法但不实现它们。文章通过代码示例详细解析了抽象类和接口的定义及实现,并讨论了它们的区别和使用场景。
|
3月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
70 1
下一篇
无影云桌面