目录
ArrayBlockingQueue简介
ArrayBlockingQueue的主要属性
put方法
take方法
ArrayBlockingQueue代码示例
LinkedBlockingQueue简介
LinkedBlockingQueue的主要属性
put方法详解
take方法详解
LinkedBlockingQueue代码示例
ArrayBlockingQueue与LinkedBlockingQueue的比较
ArrayBlockingQueue 是一个基于数组的有界阻塞队列,是线程安全的。它的容量是固定的,创建时需要指定队列的大小。ArrayBlockingQueue 使用一个可重入锁来保证线程安全,并使用条件变量来实现在队列为空或队列已满时线程的阻塞与唤醒。
ArrayBlockingQueue 的特点如下:
ArrayBlockingQueue 的实现原理如下:
ArrayBlockingQueue 提供了以下常用方法:
总之,ArrayBlockingQueue 是一种高效、线程安全的数据结构,适用于多线程环境下的生产者-消费者问题,其中生产者线程负责将元素插入队尾,消费者线程负责从队首取出元素进行处理。它还可以用于有界任务调度、有界缓冲区等场景,限制了队列中元素的数量。
ArrayBlockingQueue的主要属性包括:
ArrayBlockingQueue使用数组来实现数据存储。为了保证线程安全,它使用了ReentrantLock(lock)(看这篇文章的详细介绍)作为主要锁,并使用了Condition(notEmpty和notFull)来实现可阻塞式的插入和删除操作。
在构造方法中,会创建lock、notEmpty和notFull等属性。
具体来说,当队列已满时,put操作将线程放置到notFull等待队列中,直到队列有空位可以插入数据。而当队列为空时,take操作将线程放置到notEmpty等待队列中,直到队列有数据可以被消费。
put方法通过判断队列是否已满来决定是否阻塞线程,如果队列已满,则将线程放入notFull等待队列中;如果满足插入数据的条件,则进行数据插入并通知消费者线程。
其方法签名如下:
public void put(E element) throws InterruptedException
element:要插入队列的元素。
put 方法的步骤如下:
如果在等待期间被中断,将抛出 InterruptedException 异常,并且会在抛出异常前恢复中断状态。
take方法通过判断队列是否为空来决定是否阻塞线程,如果队列为空,则将线程放入notEmpty等待队列中;如果队列不为空,则获取并移除数据,并通知生产者线程。
其方法签名如下:
public E take() throws InterruptedException
take 方法的步骤如下:
如果在等待期间被中断,将抛出 InterruptedException 异常,并且会在抛出异常前恢复中断状态。
通过以上分析,可以看出ArrayBlockingQueue通过Condition的通知机制实现了可阻塞式的插入和获取操作。
import java.util.concurrent.ArrayBlockingQueue;
public class main {
private static final int BUFFER_SIZE = 10;
private static final int NUM_PRODUCERS = 3;
private static final int NUM_CONSUMERS = 2;
public static void main(String[] args) {
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(BUFFER_SIZE);
// 创建生产者线程
for (int i = 0; i < NUM_PRODUCERS; i++) {
Thread producer = new Thread(() -> {
try {
int count = 1;
while (true) {
queue.put(count);
System.out.println(Thread.currentThread().getName() + " 生产: " + count);
count++;
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
}
// 创建消费者线程
for (int i = 0; i < NUM_CONSUMERS; i++) {
Thread consumer = new Thread(() -> {
try {
while (true) {
int data = queue.take();
System.out.println(Thread.currentThread().getName() + " 已消耗: " + data);
Thread.sleep(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
consumer.start();
}
try {
// 主线程等待一定时间后停止生产者和消费者线程
Thread.sleep(10000);
System.out.println("停止生产者和消费者...");
// 中断所有生产者和消费者线程
Thread.currentThread().getThreadGroup().interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们创建了3个生产者线程和2个消费者线程。每个生产者线程不断向队列中放入递增的数据,每个消费者线程不断从队列中取出数据进行消费。
主线程等待10秒后,通过中断方式停止所有生产者和消费者线程。在每个生产者和消费者线程的循环中,检查线程是否被中断,如果是则退出循环并终止线程。
这个示例展示了如何使用ArrayBlockingQueue实现多生产者和多消费者之间的协作,实现安全的线程间数据交换。
LinkedBlockingQueue是Java中的一个阻塞队列实现,它基于链表数据结构实现,可以用于在生产者和消费者之间安全地传递数据。
该队列的特点如下:
LinkedBlockingQueue是一个基于链表的阻塞队列,其实现原理如下:
通过以上的实现机制,LinkedBlockingQueue能够实现线程安全的插入和删除操作,并且支持阻塞和非阻塞的操作。它提供了put、take等方法来实现元素的添加和移除,并且在队列为空或已满时能够自动阻塞和唤醒相应的线程,以保证生产者和消费者之间的同步和协作。
具体使用LinkedBlockingQueue时,可以通过以下方法进行操作:
除了上述方法外,LinkedBlockingQueue还提供了其他一些方法,例如size()返回队列的元素数量,isEmpty()判断队列是否为空,clear()清空队列等。
LinkedBlockingQueue提供了线程安全的操作,适用于多线程环境下的生产者和消费者模型。它的阻塞特性使得生产者和消费者线程可以有效地进行同步,以实现数据的安全传递。
put方法是LinkedBlockingQueue类提供的一个用于向队列中添加元素的方法。具体详解如下:
take方法是LinkedBlockingQueue类提供的一个用于从队列中取出元素的方法。具体详解如下:
需要注意的是,put和take方法都是阻塞操作,它们会导致当前线程等待直到满足添加或移除的条件。这使得LinkedBlockingQueue可以作为一种线程安全的同步机制,用于实现生产者和消费者之间的协作。当队列为空时,take方法会阻塞消费者线程,直到有新的元素可供消费。当队列已满时,put方法会阻塞生产者线程,直到有空闲空间可供添加新的元素。
import java.util.concurrent.LinkedBlockingQueue;
public class main {
public static void main(String[] args) {
LinkedBlockingQueue queue = new LinkedBlockingQueue<>(10);
// 启动生产者线程
for (int i = 1; i <= 3; i++) {
final int producerId = i;
new Thread(() -> {
for (int j = 1; j <= 5; j++) {
try {
int element = producerId * 10 + j;
System.out.println("生产者" + producerId + "生产元素:" + element);
queue.put(element); // 将元素添加到队列
Thread.sleep((long) (Math.random() * 1000)); // 模拟生产过程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
// 启动消费者线程
for (int i = 1; i <= 2; i++) {
final int consumerId = i;
new Thread(() -> {
while (true) {
try {
Integer element = queue.take(); // 从队列中取出元素
System.out.println("消费者" + consumerId + "消费元素:" + element);
Thread.sleep((long) (Math.random() * 2000)); // 模拟消费过程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
在上述代码中,我们创建了一个最大容量为10的LinkedBlockingQueue对象。然后启动了3个生产者线程和2个消费者线程。生产者线程通过循环向队列中添加元素,消费者线程通过循环从队列中取出元素,并打印到控制台上。
为了模拟真实的生产和消费过程,我们在生产者和消费者线程中都添加了随机的睡眠操作。
运行上述代码,可以看到类似以下的输出结果:
生产者1生产元素:11
生产者3生产元素:31
生产者2生产元素:21
消费者1消费元素:11
消费者2消费元素:31
生产者3生产元素:32
生产者3生产元素:33
生产者1生产元素:12
生产者1生产元素:13
生产者2生产元素:22
生产者1生产元素:14
消费者2消费元素:21
消费者2消费元素:32
生产者3生产元素:34
生产者1生产元素:15
生产者2生产元素:23
消费者1消费元素:33
生产者3生产元素:35
生产者2生产元素:24
消费者2消费元素:12
生产者2生产元素:25
消费者1消费元素:13
消费者1消费元素:22
消费者2消费元素:14
消费者1消费元素:34
消费者1消费元素:15
消费者2消费元素:23
消费者2消费元素:35
消费者1消费元素:24
消费者2消费元素:25
在这个例子中,我们使用了多个生产者和消费者线程,它们同时操作LinkedBlockingQueue,展示了队列的并发性质。
相同点:ArrayBlockingQueue和LinkedBlockingQueue都是通过condition通知机制来实现可阻塞式插入和删除元素,并满足线程安全的特性。
不同点:
总的来说,如果对并发度要求不高,且需要固定大小的有界队列,可以选择ArrayBlockingQueue;如果对并发度要求较高,且需要动态调整大小的无界队列,可以选择LinkedBlockingQueue。