哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云;欢迎大家常来逛逛
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
前言
在并发编程中,线程之间相互合作执行任务,其中数据传输是至关重要的。对于多个线程访问共享数据的情况下,我们需要保证数据的正确性和一致性。Java提供了多种高效的线程安全容器来满足这种需求。其中一种是 ArrayBlockingQueue
,它是一个基于数组的有界队列,可以安全地同时被多个线程使用。
摘要
本文将介绍 ArrayBlockingQueue
的基本概念、源代码解析、应用场景案例以及优缺点分析。
ArrayBlockingQueue
简介
ArrayBlockingQueue
是一个有界队列,基于数组实现。它按照先进先出的原则对元素进行排序。当队列已满时,生产者线程将被阻塞,直到有空间可用;当队列为空时,消费者线程将被阻塞,直到有元素可用。
ArrayBlockingQueue
的主要方法如下:
put(E e)
:将指定元素添加到此队列的尾部,如果队列已满则阻塞直到队列有空间可用。take()
:获取并移除此队列的头元素,如果队列为空则阻塞直到队列有元素可用。offer(E e)
:将指定元素插入此队列的尾部,如果队列已满则返回false
。poll()
:获取并移除此队列的头元素,如果队列为空则返回null
。remainingCapacity()
:返回此队列中剩余的可用空间。size()
:返回此队列中的元素数量。
源代码解析
这里简要分析一下 ArrayBlockingQueue
的源代码。
类定义
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -817911632652898426L;
final Object[] items; // 存储元素的数组
int takeIndex; // 队列头部元素的索引
int putIndex; // 队列尾部元素的索引
int count; // 已经添加到队列中的元素数量
final ReentrantLock lock; // 可重入锁
private final Condition notEmpty; // 不空条件
private final Condition notFull; // 不满条件
// 省略构造方法和其他细节
}
可以看出 ArrayBlockingQueue
实际上是一个 Object
类型的数组,同时维护了队列头部元素的索引、队列尾部元素的索引、已经添加到队列中的元素数量、可重入锁、不空条件和不满条件等属性。
如下是部分源码截图:
put() 方法
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e); // 对象不能为空
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取可中断锁
try {
while (count == items.length) { // 队列已满
notFull.await(); // 队列不满条件等待
}
enqueue(e); // 入队
} finally {
lock.unlock(); // 释放锁
}
}
put()
方法将指定元素添加到队列的尾部。如果队列已满,则阻塞等待直到队列有空间可用。在执行该方法时,线程会获取可中断锁并进入临界区。若队列已满,则线程调用 notFull.await()
方法进入条件等待状态。当其他线程调用 take()
方法或 poll()
方法取走了队列中的元素并释放了空间,就会调用 notEmpty.signal()
方法通知 notFull
等待队列,此时线程会继续从 while
循环中进行判断是否需要继续等待。
如下是部分源码截图:
take() 方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取可中断锁
try {
while (count == 0) { // 队列为空
notEmpty.await(); // 队列不空条件等待
}
return dequeue(); // 出队
} finally {
lock.unlock(); // 释放锁
}
}
take()
方法获取并移除队列头部的元素,如果队列为空,则阻塞等待直到队列有元素可用。在执行该方法时,线程会获取可中断锁并进入临界区。若队列为空,则线程调用 notEmpty.await()
方法进入条件等待状态。当其他线程调用 put()
方法或 offer()
方法插入了元素并释放了空间,就会调用 notFull.signal()
方法通知 notEmpty
等待队列,此时线程会继续从 while
循环中进行判断是否需要继续等待。
如下是部分源码截图:
应用场景案例
假设一个场景,有一个生产者和多个消费者,生产者不断往队列中添加元素,消费者不断从队列中取出元素进行处理。
import java.util.concurrent.ArrayBlockingQueue;
public class ProducerConsumer {
public static void main(String[] args) {
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
new Thread(() -> {
for (int i = 1; i <= 20; i++) {
try {
queue.put(i);
System.out.println("生产了:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
while (true) {
try {
Integer num = queue.take();
System.out.println("消费了:" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
在上述程序中,我们创建了一个容量为 10 的 ArrayBlockingQueue
队列。生产者线程不断向队列中添加元素,消费者线程不断从队列中获取元素进行消费。当队列已满时,生产者线程将被阻塞,直到队列中有空间可用;当队列为空时,消费者线程将被阻塞,直到队列中有元素可用。
根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。
优缺点分析
优点
ArrayBlockingQueue
是线程安全的,可以安全地同时被多个线程使用。- 它具有高效的入队和出队操作,可以快速地插入和删除数据。
- 由于它是一个有界队列,因此它可以帮助我们避免 OutOfMemoryError 的问题,防止无限制地添加数据。
缺点
ArrayBlockingQueue
的容量是固定的,因此在某些场景下可能会受到限制。
类代码方法介绍
下面是 ArrayBlockingQueue
类的主要方法介绍:
方法名 | 描述 |
---|---|
put(E e) | 将指定元素添加到此队列的尾部,如果队列已满则阻塞直到队列有空间可用。 |
take() | 获取并移除此队列的头元素,如果队列为空则阻塞直到队列有元素可用。 |
offer(E e) | 将指定元素插入此队列的尾部,如果队列已满则返回 false 。 |
poll() | 获取并移除此队列的头元素,如果队列为空则返回 null 。 |
remainingCapacity() | 返回此队列中剩余的可用空间。 |
size() | 返回此队列中的元素数量。 |
iterator() | 返回此队列元素的迭代器。 |
测试用例
根据如上理论知识点对ArrayBlockingQueue的盘点,这里我再给大家做个简单的测试用例进行使用演示:
测试代码演示
package com.example.javase.collection;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @Author ms
* @Date 2023-10-24 13:31
*/
public class ArrayBlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
// 生产者线程
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
queue.put(i);
System.out.println("生产了:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 消费者线程
new Thread(() -> {
while (true) {
try {
Integer num = queue.take();
System.out.println("消费了:" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 等待一段时间后结束程序
Thread.sleep(1000);
System.exit(0);
}
}
测试结果
根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。
代码分析
根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。
如上测试用例演示了使用Java中的ArrayBlockingQueue
类实现生产者和消费者模型。
ArrayBlockingQueue
是Java中的一个阻塞队列,具有以下特点:
-
有固定的容量,一旦队列满了,再往里面放元素就会阻塞直到有元素被取出。
-
当队列为空时,从队列中取元素会被阻塞,直到有元素被加入。
在该代码中,定义了一个ArrayBlockingQueue对象queue,并指定了容量为5。
然后启动了两个线程,一个是生产者线程,一个是消费者线程。
生产者线程不断向队列中put元素,消费者线程不断从队列中take元素,实现了生产者和消费者的异步操作。
最后通过让主线程睡眠一段时间,然后结束程序,来结束整个程序。
小结
本文介绍了 Java 多线程编程中的 ArrayBlockingQueue
,包括其基本概念、源代码解析、应用场景案例以及优缺点分析。总体来说,ArrayBlockingQueue
是一种高效的线程安全容器,适用于生产者消费者场景,能够优化多线程之间的数据传输和共享。不过需要注意的是,它是一个固定容量的有界队列,因此在某些场景下可能会受到限制。
总结
在多线程编程中,线程之间需要相互协作执行任务,数据传输起着重要的作用。为了保证多个线程访问共享数据时的正确性和一致性,Java提供了多种高效的线程安全容器,其中一个就是 ArrayBlockingQueue
。
ArrayBlockingQueue
是一个基于数组实现的有界队列,在多个线程访问共享数据时,可以安全地同时被多个线程使用。它按照先进先出的原则对元素进行排序,当队列已满时,生产者线程将被阻塞,直到队列有空间可用;当队列为空时,消费者线程将被阻塞,直到队列中有元素可用。它还具有高效的入队和出队操作,可以快速地插入和删除数据。
但是,ArrayBlockingQueue
的容量是固定的,因此在某些场景下可能会受到限制。在实际应用中,我们需要根据具体的需求来选择使用合适的线程安全容器。
总之,了解和掌握 ArrayBlockingQueue
的使用方法可以帮助我们更好地设计和实现多线程应用,提高程序的并发性和安全性。
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。