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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: LinkedBlockingQueue类是以链表结构实现高效线程安全队列,具有出色的并发性能、灵活的阻塞与非阻塞操作,以及适用于生产者和消费者模式的能力,此外,LinkedBlockingQueue还具有高度的可伸缩性,能够在多线程环境中有效管理数据共享,是提升程序并发性能和稳定性的关键组件。

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

内容概要

LinkedBlockingQueue类是以链表结构实现高效线程安全队列,具有出色的并发性能、灵活的阻塞与非阻塞操作,以及适用于生产者和消费者模式的能力,此外,LinkedBlockingQueue还具有高度的可伸缩性,能够在多线程环境中有效管理数据共享,是提升程序并发性能和稳定性的关键组件。

核心概念

假如有一个在线购物平台,这个平台需要处理大量的订单,每当有用户下单,系统就需要将这个订单信息传递给后台的处理程序进行进一步的处理,比如库存管理、支付处理、物流分配等,但是,由于用户量巨大,订单量也非常大,如果直接让处理程序立即处理每一个订单,那么系统可能会因为处理不过来而崩溃。

这是,可以使用LinkedBlockingQueue来解决这问题,可以把LinkedBlockingQueue想象成一个排队等候区,每当有用户下单,订单信息就被放到这个排队等候区里面,后台的处理程序则可以从这个排队等候区里面取出订单进行处理,由于LinkedBlockingQueue是一个线程安全的队列,所以它可以保证在多线程环境下,订单信息不会被重复处理或丢失。

而且,LinkedBlockingQueue还有一个很有用的特性,就是它可以在插入元素时设置等待时间,因此,如果排队等候区已经满了(达到了最大容量),新的订单信息就需要等待直到有空间可以放入,通过设置等待时间,可以控制新订单信息需要等待多久,如果超过了这个时间还没有空间,那么就可以抛出一个异常或者进行其他的处理。

LinkedBlockingQueue主要用来解决多线程间的数据共享和传输问题,尤其是在生产者-消费者场景中。这是一个线程安全的阻塞队列,它实现了BlockingQueue接口,并基于链表数据结构。

LinkedBlockingQueue通常用来解决以下情况问题:

  1. 线程安全的数据共享:在多线程环境中,当多个线程需要访问和修改共享数据时,可能会出现数据不一致的问题,LinkedBlockingQueue通过内部的锁机制和其他同步措施,确保在并发情况下数据的完整性和一致性。
  2. 生产者-消费者问题:这是并发编程中的一个经典问题,其中生产者生成数据放入缓冲区,消费者从缓冲区取出数据,如果缓冲区已满,生产者需要等待;如果缓冲区为空,消费者需要等待,LinkedBlockingQueue提供了阻塞的put()take()方法,使得生产者和消费者可以在队列满或空时自动阻塞等待,从而简化了编程复杂度。
  3. 流量控制:在高并发系统中,如果后端处理速度跟不上前端生成数据的速度,可能会导致系统崩溃,通过使用LinkedBlockingQueue,可以设置一个有限的队列容量,当队列满时,生产者会被阻塞,从而实现了对流量的控制。
  4. 解耦LinkedBlockingQueue还可以用于解耦生产者和消费者之间的直接依赖关系,生产者只需要将数据放入队列,而不需要关心消费者何时或如何消费这些数据;同样,消费者只需要从队列中取出数据处理,而不需要关心数据是谁生产的或如何生产的。这种解耦有助于提高系统的可维护性和可扩展性。

代码案例

下面使用LinkedBlockingQueue类模拟了一个生产者-消费者场景,其中生产者生成整数并将它们放入队列,而消费者从队列中取出整数并处理它们,如下:

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

// 生产者类,用于生成数据并放入队列  
class Producer implements Runnable {
   
     
    private final BlockingQueue<Integer> queue;  

    public Producer(BlockingQueue<Integer> queue) {
   
     
        this.queue = queue;  
    }  

    @Override  
    public void run() {
   
     
        try {
   
     
            for (int i = 0; i < 10; i++) {
   
     
                Thread.sleep(200); // 模拟生产时间  
                int item = i;  
                System.out.println("生产者生产了: " + item);  
                queue.put(item); // 将生产的数据放入队列  
            }  
        } catch (InterruptedException e) {
   
     
            Thread.currentThread().interrupt();  
        }  
    }  
}  

// 消费者类,用于从队列中取出数据并处理  
class Consumer implements Runnable {
   
     
    private final BlockingQueue<Integer> queue;  

    public Consumer(BlockingQueue<Integer> queue) {
   
     
        this.queue = queue;  
    }  

    @Override  
    public void run() {
   
     
        try {
   
     
            while (true) {
   
     
                Integer item = queue.take(); // 从队列中取出数据,如果队列为空则阻塞  
                System.out.println("消费者消费了: " + item);  
                if (item == 9) {
   
    // 假设生产10个元素后结束消费  
                    break;  
                }  
            }  
        } catch (InterruptedException e) {
   
     
            Thread.currentThread().interrupt();  
        }  
    }  
}  

// 主类,用于演示生产者和消费者的使用  
public class LinkedBlockingQueueDemo {
   
     

    public static void main(String[] args) {
   
     
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10); // 创建一个容量为10的LinkedBlockingQueue  

        // 启动生产者线程  
        Thread producerThread = new Thread(new Producer(queue));  
        producerThread.start();  

        // 启动消费者线程  
        Thread consumerThread = new Thread(new Consumer(queue));  
        consumerThread.start();  

        // 等待两个线程执行完成(在实际应用中,可能需要使用更复杂的同步机制来等待线程结束)  
        try {
   
     
            producerThread.join();  
            consumerThread.join();  
        } catch (InterruptedException e) {
   
     
            e.printStackTrace();  
        }  

        System.out.println("程序执行完毕");  
    }  
}

在上面代码中,定义了一个生产者类Producer和一个消费者类Consumer,它们都实现了Runnable接口,因此可以作为线程运行,生产者在其run方法中循环生成整数,并通过queue.put(item)方法将它们放入队列中,消费者在其run方法中通过queue.take()方法从队列中取出整数并处理它们,如果队列为空,take()方法会阻塞,直到队列中有元素可用。

main方法中,创建了一个容量为10的LinkedBlockingQueue实例,并分别启动了一个生产者线程和一个消费者线程,程序会等待这两个线程执行完成后输出“程序执行完毕”。

核心API

LinkedBlockingQueue是实现了BlockingQueue接口,是一个基于链表的、线程安全的阻塞队列,以下是LinkedBlockingQueue类中一些主要方法的含义:

  1. 构造方法
    • LinkedBlockingQueue():创建一个具有默认容量(Integer.MAX_VALUE)的LinkedBlockingQueue
    • LinkedBlockingQueue(int capacity):创建一个具有指定容量的LinkedBlockingQueue
  2. 添加元素
    • add(E e):将指定的元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回true,如果当前没有可用的空间,则抛出IllegalStateException
    • offer(E e):将指定的元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回true,如果当前没有可用的空间,则返回false
    • put(E e) throws InterruptedException:将指定的元素插入此队列中,等待必要的空间可用;如果空间不可用,则等待直到空间可用或线程被中断。
    • offer(E e, long timeout, TimeUnit unit):将指定的元素插入此队列中,等待指定的时间以允许其他线程插入或移除元素,或者直到此线程被中断。
  3. 移除元素
    • remove():移除并返回此队列的头部,如果此队列为空,则抛出NoSuchElementException
    • poll():移除并返回此队列的头部,如果此队列为空,则返回null
    • take() throws InterruptedException:移除并返回此队列的头部,等待(如果必要)直到元素变得可用或线程被中断。
    • poll(long timeout, TimeUnit unit):移除并返回此队列的头部,等待指定的时间以允许其他线程插入元素,或者直到此线程被中断或超时。
  4. 检查元素
    • element():检索但不移除此队列的头部,如果此队列为空,则抛出NoSuchElementException
    • peek():检索但不移除此队列的头部,如果此队列为空,则返回null
  5. 其它有用的方法
    • size():返回队列中的元素数量。这个方法的精确度可能会受到并发修改的影响,因此它主要用于监控,而不是用于同步控制。
    • remainingCapacity():返回理想情况下(没有内存和资源约束)此队列可接受的额外元素数量。
    • clear():移除队列中的所有元素。
    • contains(Object o):检查队列中是否包含指定的元素。
    • drainTo(Collection<? super E> c)drainTo(Collection<? super E> c, int maxElements):将队列中的元素移除并添加到指定的集合中,直到队列为空或移除了指定数量的元素。
    • iterator():返回队列中元素的迭代器。注意,迭代器的remove()方法会删除队列中的元素,但是不会自动调整队列的容量。
    • toArray():返回包含队列中所有元素的数组。

核心总结

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

LinkedBlockingQueue实现了BlockingQueue接口,以链表结构存储元素,保证了线程安全,其优点在于具有高效的并发性能和可伸缩性,适用于生产者和消费者模式,能够很好地处理多线程间的数据共享问题,它的缺点是在比如数据量非常大时,由于链表结构的内存开销,可能会占用较多内存,此外,虽然它提供了阻塞和非阻塞的操作,但在高并发场景下,线程间的竞争可能导致性能下降。

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

END!

往期回顾

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

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

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

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

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

相关文章
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
3天前
|
存储 分布式计算 Java
存算分离与计算向数据移动:深度解析与Java实现
【11月更文挑战第10天】随着大数据时代的到来,数据量的激增给传统的数据处理架构带来了巨大的挑战。传统的“存算一体”架构,即计算资源与存储资源紧密耦合,在处理海量数据时逐渐显露出其局限性。为了应对这些挑战,存算分离(Disaggregated Storage and Compute Architecture)和计算向数据移动(Compute Moves to Data)两种架构应运而生,成为大数据处理领域的热门技术。
17 2
|
3天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
3天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
6 0
|
安全 Java Linux
Java LinkedBlockingQueue 实现
本文着重介绍 Java 并发容器中 LinkedBlockingQueue 的实现方式。
|
12天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
91 38
|
9天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
23 4
|
5天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
25 3
|
11天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。

推荐镜像

更多