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全面解析!

相关文章
|
24天前
|
Java
【Java】一个简单的接口例子(帮助理解接口+多态)
【Java】一个简单的接口例子(帮助理解接口+多态)
16 0
|
3天前
|
安全 Java 编译器
接口之美,内部之妙:深入解析Java的接口与内部类
接口之美,内部之妙:深入解析Java的接口与内部类
8 0
接口之美,内部之妙:深入解析Java的接口与内部类
|
6天前
|
缓存 安全 Java
Java中函数式接口详解
Java 8引入函数式接口,支持函数式编程。这些接口有单一抽象方法,可与Lambda表达式结合,简化代码。常见函数式接口包括:`Function&lt;T, R&gt;`用于转换操作,`Predicate&lt;T&gt;`用于布尔判断,`Consumer&lt;T&gt;`用于消费输入,`Supplier&lt;T&gt;`用于无参生成结果。开发者也可自定义函数式接口。Lambda表达式使实现接口更简洁。注意异常处理和线程安全。函数式接口广泛应用于集合操作、并行编程和事件处理。提升代码可读性和效率,是现代Java开发的重要工具。
18 0
|
6天前
|
Java 关系型数据库 MySQL
大厂面试题详解:Java抽象类与接口的概念及区别
字节跳动大厂面试题详解:Java抽象类与接口的概念及区别
30 0
|
7天前
|
Java
Java中的多线程实现:使用Thread类与Runnable接口
【4月更文挑战第8天】本文将详细介绍Java中实现多线程的两种方法:使用Thread类和实现Runnable接口。我们将通过实例代码展示如何创建和管理线程,以及如何处理线程同步问题。最后,我们将比较这两种方法的优缺点,以帮助读者在实际开发中选择合适的多线程实现方式。
18 4
|
20天前
|
算法 安全 Java
Java中的并发编程:理解并发性能优化
在当今软件开发领域,多核处理器的普及使得并发编程变得更加重要。本文将深入探讨Java中的并发编程,介绍并发性能优化的关键技术,帮助开发人员更好地利用多核处理器提升应用程序性能。
|
24天前
|
Java
【Java】Clonable 接口
【Java】Clonable 接口
11 1
|
1月前
|
安全 Java API
Java并发 - J.U.C并发容器类 list、set、queue
Queue API 阻塞是通过 condition 来实现的,可参考 Java 并发 - Lock 接口 ArrayBlockingQueue 阻塞 LinkedBlockingQueue 阻塞 ArrayQueue 非阻塞 LinkedQueue 非阻塞
|
3月前
|
安全 Java 编译器
Java并发编程学习6-同步容器类和并发容器
【1月更文挑战第6天】本篇介绍同步容器类和并发容器的相关内容(Vector、ConcurrentHashMap、CopyOnWriteArrayList)
34 3
Java并发编程学习6-同步容器类和并发容器
|
10月前
|
存储 安全 算法
【Java并发编程 十一】JUC并发包下并发容器类(下)
【Java并发编程 十一】JUC并发包下并发容器类(下)
73 0