阻塞队列BlockingQueue,看完就会了

开发 前端
当生产者尝试向已满的有界队列添加元素时,添加方法(比如put()),操作可能会阻塞,直到队列中有可用空间。这种特性使得有界队列在某些场景下,能自动实现限流,避免系统资源过度消耗。

在Java并发编程中,生产者-消费者问题是一个常见的需求。java.util.concurrent.BlockingQueue接口为解决这类问题提供了强大便捷的支持。

不仅提供了在多线程环境下安全添加和获取元素的方法,还通过阻塞机制确保了生产者和消费者之间的协调。

接下来,我们一起看看BlockingQueue的特性、方法以及如何使用它构建高效的多线程应用程序。

一、BlockingQueue类型

(一)无界队列

无界队列可以在理论上无限增长,在Java中创建无界BlockingQueue非常简便:

BlockingQueue<String> unboundedQueue = new LinkedBlockingDeque<>();

此队列的容量默认是Integer.MAX_VALUE,虽然有默认边界,但在实际应用中,若生产者持续快速生产元素,而消费者无法及时消费,可能导致内存占用不断增加,最终引发OOM。所以说,虽然默认有界,实际相当于无界。

(二)有界队列

有界队列则具有明确的最大容量限制,创建方式如下:

BlockingQueue<Integer> boundedQueue = new LinkedBlockingDeque<>(10);

这里创建了一个容量为10的BlockingQueue。

当生产者尝试向已满的有界队列添加元素时,添加方法(比如put()),操作可能会阻塞,直到队列中有可用空间。这种特性使得有界队列在某些场景下,能自动实现限流,避免系统资源过度消耗。

二、BlockingQueue的方法

(一)添加元素

add(E e)

尝试将指定元素添加到队列中。若添加成功,返回true;若队列已满,则抛出IllegalStateException异常。比如:

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
try {
    boolean success = queue.add(10);
    if (success) {
        System.out.println("元素添加成功");
    }
} catch (IllegalStateException e) {
    System.out.println("队列已满,添加元素失败");
}

put(E e)

将元素插入队列,如果队列已满,则阻塞当前线程,直到有可用空间。比如:

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
try {
    queue.put(20);
    System.out.println("元素已放入队列");
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    System.out.println("线程被中断,添加元素失败");
}

offer(E e)

尝试将元素添加到队列中。若添加成功,返回true;若队列已满,则返回false。比如:

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
boolean result = queue.offer(30);
if (result) {
    System.out.println("元素添加成功");
} else {
    System.out.println("队列已满,添加元素失败");
}

offer(E e, long timeout, TimeUnit unit)

在指定的超时时间内尝试将元素插入队列。若在超时时间内成功插入,返回true;否则返回false。比如:

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
try {
    boolean success = queue.offer(40, 2, TimeUnit.SECONDS);
    if (success) {
        System.out.println("元素在超时时间内添加成功");
    } else {
        System.out.println("在超时时间内未能添加元素,队列可能已满");
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    System.out.println("线程被中断,添加元素失败");
}

(二)检索元素

take()

从队列中获取并移除头部元素。如果队列为空,当前线程将被阻塞,直到有元素可用。

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
// 假设队列中已有元素
try {
    Integer element = queue.take();
    System.out.println("取出的元素为: " + element);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    System.out.println("线程被中断,获取元素失败");
}

poll(long timeout, TimeUnit unit)

检索并移除队列头部元素。若在指定的超时时间内有元素可用,则返回该元素;若超时时间内仍无元素可用,则返回null。

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
try {
    Integer element = queue.poll(1, TimeUnit.SECONDS);
    if (element!= null) {
        System.out.println("在超时时间内取出的元素为: " + element);
    } else {
        System.out.println("在超时时间内未获取到元素,队列可能为空");
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    System.out.println("线程被中断,获取元素失败");
}

三、多线程生产者-消费者

我们将模拟一个简单的生产-消费场景,假设有一个消息队列,多个生产者线程不断向队列中生产消息(这里简化为随机整数),多个消费者线程从队列中获取消息并进行处理(这里简化为打印消息和线程名)。为了能够有效结束,增加poisonPill参数。

首先是生产者:

public class Producer implements Runnable {
    private final BlockingQueue<Integer> queue;
    private final int poisonPill;
    private final int poisonPillPerProducer;

    public Producer(BlockingQueue<Integer> queue, int poisonPill, int poisonPillPerProducer) {
        this.queue = queue;
        this.poisonPill = poisonPill;
        this.poisonPillPerProducer = poisonPillPerProducer;
    }

    @Override
    public void run() {
        try {
            produceMessages();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void produceMessages() throws InterruptedException {
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            int message = random.nextInt(100);
            queue.put(message);
        }
        for (int j = 0; j < poisonPillPerProducer; j++) {
            queue.put(poisonPill);
        }
    }
}

生产者构造函数接受一个BlockingQueue用于与消费者通信,还接受poisonPill和每个生产者应发送poisonPill值的数量。在produceMessages方法中,先生产100个随机消息放入队列,然后发送指定数量的毒丸。

接着是消费者:

public class Consumer implements Runnable {
    private final BlockingQueue<Integer> queue;
    private final int poisonPill;

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

    @Override
    public void run() {
        try {
            consumeMessages();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void consumeMessages() throws InterruptedException {
        while (true) {
            Integer message = queue.take();
            if (message.equals(poisonPill)) {
                return;
            }
            System.out.println(Thread.currentThread().getName() + " 消费消息: " + message);
        }
    }
}

消费者构造函数接受BlockingQueue和poisonPill值。在consumeMessages方法中,不断从队列获取消息,如果是等于poisonPill则结束消费,否则打印消息和线程名。

最后是主程序类来启动生产者和消费者线程:

final int BOUND = 10;
final int N_PRODUCERS = 3;
final int N_CONSUMERS = 2;
final int poisonPill = Integer.MAX_VALUE;
final int poisonPillPerProducer = N_CONSUMERS / N_PRODUCERS;
final int mod = N_CONSUMERS % N_PRODUCERS;

final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(BOUND);

for (int i = 0; i < N_PRODUCERS; i++) {
    new Thread(new Producer(queue, poisonPill, poisonPillPerProducer)).start();
}

for (int j = 0; j < N_CONSUMERS; j++) {
    new Thread(new Consumer(queue, poisonPill)).start();
}

new Thread(new Producer(queue, poisonPill, poisonPillPerProducer + mod)).start();

在主程序中,定义了队列容量、生产者和消费者数量,以及poisonPill值相关参数,创建了数量为10的有界BlockingQueue,启动了指定数量的生产者和消费者线程。

当运行上述程序时,生产者线程会不断向队列中放入随机整数,消费者线程会从队列中取出并打印这些整数,同时每个消费者接收到poisonPill后会结束执行。

运行结果如下(因为用了随机数,每次效果不同):

Thread-3 消费消息: 47
Thread-4 消费消息: 3
Thread-3 消费消息: 35
Thread-4 消费消息: 83
Thread-4 消费消息: 68
Thread-4 消费消息: 40
Thread-4 消费消息: 73
Thread-4 消费消息: 56
Thread-4 消费消息: 56
...

随着生产和消费的进行,最终所有消费者线程在接收到poisonPill后停止。

责任编辑:武晓燕 来源: 看山的小屋
相关推荐

2022-05-17 08:24:58

查询日志MySQL

2021-08-13 07:56:13

Python虚拟环境

2017-12-12 13:27:20

主板跳线USB

2020-11-27 09:16:21

BlockingQue

2018-04-27 15:33:59

Python装饰器

2017-02-09 19:45:07

Linux系统Linux 发行版

2023-06-30 08:27:20

2022-10-21 08:02:40

reduce​初始值循环

2020-06-05 18:09:14

TomcatWeb应用服务器

2023-12-06 07:28:47

阻塞IO异步IO

2020-11-19 07:41:51

ArrayBlocki

2020-11-25 14:28:56

DelayedWork

2024-02-20 08:16:10

阻塞队列源码

2020-11-24 09:04:55

PriorityBlo

2020-11-20 06:22:02

LinkedBlock

2022-07-14 08:22:48

Computedvue3

2017-04-12 10:02:21

Java阻塞队列原理分析

2024-12-26 07:49:57

Java队列线程

2020-07-14 07:46:55

NginxIPIP段

2021-10-03 15:10:19

Rust CargoNpm
点赞
收藏

51CTO技术栈公众号