java-ArrayBlockingQueue详解

2024-01-14 01:12

本文主要是介绍java-ArrayBlockingQueue详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在Java并发编程中,ArrayBlockingQueue是一个非常常用的工具类。它是一个由数组支持的有界阻塞队列,提供了线程安全的队列操作。

1.ArrayBlockingQueue概述

ArrayBlockingQueue是一个基于数组实现的阻塞队列,它继承自AbstractQueue并实现了BlockingQueue接口。这个队列在创建时需要指定一个固定的大小,之后这个大小就不能再改变了。当队列满时,如果再有新的元素试图加入队列,那么这个操作会被阻塞;同样地,如果队列为空,那么从队列中取元素的操作也会被阻塞。这种特性使得ArrayBlockingQueue非常适合作为生产者-消费者模式中的缓冲区。

2.ArrayBlockingQueue的核心特性

2.1.线程安全性

ArrayBlockingQueue是线程安全的,它通过内部锁机制保证了在多线程环境下的安全性。因此,在多线程环境中,你可以放心地使用它而不需要担心数据的一致性问题。

2.2.阻塞控制

ArrayBlockingQueue提供了阻塞控制机制。当队列满时,尝试向队列中添加元素的线程会被阻塞,直到队列中有空间可用;同样,当队列为空时,尝试从队列中取出元素的线程也会被阻塞,直到队列中有元素可供消费。这种机制可以有效地控制生产者和消费者的速度,避免资源的浪费。

2.3.有界性

ArrayBlockingQueue的有界性可以防止队列无限制地增长,从而避免内存溢出。在实际应用中,这种有界性可以作为系统的一个流量控制阀,当系统过载时,通过阻塞或拒绝请求来保护系统。

3.ArrayBlockingQueue的使用

3.1.创建ArrayBlockingQueue

创建一个ArrayBlockingQueue非常简单,只需要指定队列的大小即可:

int queueSize = 10;
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(queueSize);

3.2.生产者-消费者模式

ArrayBlockingQueue常用于生产者-消费者模式。生产者负责生成数据并添加到队列中,而消费者则从队列中取出数据并处理。下面是一个简单的生产者-消费者示例:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class ProducerConsumerExample {public static void main(String[] args) {BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(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(() -> {while (true) {try {Integer data = queue.take();System.out.println("消费者消费了数据:" + data);} catch (InterruptedException e) {e.printStackTrace();}}});producer.start();consumer.start();}
}

运行结果:

生产者生产了数据:0
消费者消费了数据:0
生产者生产了数据:1
消费者消费了数据:1
生产者生产了数据:2
消费者消费了数据:2
生产者生产了数据:3
消费者消费了数据:3
生产者生产了数据:4
消费者消费了数据:4
生产者生产了数据:5
消费者消费了数据:5
生产者生产了数据:6
消费者消费了数据:6
生产者生产了数据:7
消费者消费了数据:7
生产者生产了数据:8
消费者消费了数据:8
生产者生产了数据:9
消费者消费了数据:9

在这个示例中,我们创建了一个大小为5的ArrayBlockingQueue,然后启动了一个生产者线程和一个消费者线程。生产者线程会生成10个数据,并尝试将它们添加到队列中;消费者线程则会不断地从队列中取出数据并处理。由于队列的大小只有5,因此当生产者生产了5个数据后,它会被阻塞,直到消费者消费了一些数据释放出空间。同样地,当队列为空时,消费者线程也会被阻塞,直到生产者生产了新的数据。

4.ArrayBlockingQueue的最佳实践

4.1.选择合适的队列大小

队列的大小应根据具体的应用场景来设置。如果设置得太小,可能会导致频繁的阻塞和上下文切换,影响性能;如果设置得太大,可能会浪费内存资源。因此,在选择队列大小时,需要综合考虑系统的负载、内存资源和性能要求等因素。

4.2.合理使用阻塞方法

ArrayBlockingQueue提供了多种阻塞方法,如puttakeofferpoll等。在使用这些方法时,需要根据具体的需求来选择合适的方法。例如,如果你希望当队列满时生产者线程能够阻塞等待空间可用,那么可以使用put方法;如果你希望生产者线程在队列满时能够立即返回并做其他处理,那么可以使用offer方法。

4.3.避免死锁

在使用ArrayBlockingQueue时,需要注意避免死锁的发生。例如,不要在持有其他锁的情况下调用ArrayBlockingQueue的阻塞方法,否则可能会导致死锁。此外,还需要注意避免循环等待和饥饿等问题。

4.4.考虑使用公平策略

ArrayBlockingQueue的构造函数允许指定一个公平性参数。如果设置为true,等待时间最长的线程将优先获得访问队列的机会。但需要注意的是,公平性可能会降低性能。因此,在决定是否使用公平策略时,需要综合考虑系统的性能和公平性要求。

5.源码详解

5.1.主要属性

// 用于存储队列元素的数组
final Object[] items;// 队列的容量
int count;// 控制并发访问的锁
final ReentrantLock lock;// 队列不满时的等待条件
private final Condition notFull;// 队列不为空时的等待条件
private final Condition notEmpty;// 队列中等待取数据的线程数
final AtomicInteger waitingConsumers = new AtomicInteger();// 队列中等待插入数据的线程数
final AtomicInteger waitingProducers = new AtomicInteger();

5.2.构造函数

ArrayBlockingQueue 提供了几种构造函数,其中最基本的两个是接受队列容量和指定是否公平的构造函数。

public ArrayBlockingQueue(int capacity) {this(capacity, false);
}public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity <= 0)throw new IllegalArgumentException();this.items = new Object[capacity];lock = new ReentrantLock(fair);notEmpty = lock.newCondition();notFull = lock.newCondition();
}

5.3.入队操作

put(E e)offer(E e) 是两种入队操作,其中 put 方法在队列满时会阻塞,而 offer 方法在队列满时会立即返回失败或者根据提供的超时时间等待。

public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length)notFull.await();enqueue(e);} finally {lock.unlock();}
}public boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException {checkNotNull(e);long nanos = unit.toNanos(timeout);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length) {if (nanos <= 0)return false;nanos = notFull.awaitNanos(nanos);}enqueue(e);return true;} finally {lock.unlock();}
}private void enqueue(E x) {// 队列尾部插入元素final Object[] items = this.items;items[putIndex] = x;if (++putIndex == items.length)putIndex = 0;count++;// 通知可能在等待的消费者线程notEmpty.signal();
}

5.4.出队操作

take()poll() 是两种出队操作,其中 take 方法在队列空时会阻塞,而 poll 方法在队列空时会立即返回 null 或者根据提供的超时时间等待。

public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}
}public E poll(long timeout, TimeUnit unit) throws InterruptedException {long nanos = unit.toNanos(timeout);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0) {if (nanos <= 0)return null;nanos = notEmpty.awaitNanos(nanos);}return dequeue();} finally {lock.unlock();}
}private E dequeue() {// 队列头部取出元素final Object[] items = this.items;@SuppressWarnings("unchecked")E x = (E) items[takeIndex];items[takeIndex] = null;if (++takeIndex == items.length)takeIndex = 0;count--;if (itrs != null)itrs.elementDequeued();// 通知可能在等待的生产者线程notFull.signal();return x;
}

6.总结

ArrayBlockingQueue是Java并发编程中一个非常实用的工具类。它提供了线程安全的阻塞队列实现,支持生产者-消费者模式,并允许通过队列的大小来控制系统的流量。在使用ArrayBlockingQueue时,需要注意选择合适的队列大小、合理使用阻塞方法、避免死锁和考虑使用公平策略等问题。通过合理地使用ArrayBlockingQueue,可以有效地提高系统的并发性能和稳定性。

这篇关于java-ArrayBlockingQueue详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/603437

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Debezium 与 Apache Kafka 的集成方式步骤详解

《Debezium与ApacheKafka的集成方式步骤详解》本文详细介绍了如何将Debezium与ApacheKafka集成,包括集成概述、步骤、注意事项等,通过KafkaConnect,D... 目录一、集成概述二、集成步骤1. 准备 Kafka 环境2. 配置 Kafka Connect3. 安装 D

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.