Java 并发编程:BlockingQueue vs wait/notify 详解

Java 并发编程:BlockingQueue vs wait/notify 详解

在并发编程中,生产者-消费者模式 是一个经典模式,日志异步写入,请求通过队列进行削峰填谷,任务拆解下发多个工作线程执行 等等场景 都非常适合这个模式来解决。

生产者不断地往缓冲区放数据,消费者不断地从缓冲区取数据。如何保证线程安全、同步有序,就是核心问题。

Java 提供了两种常见解决方案:

  1. 使用 wait()notify() 进行线程通信。
  2. 使用 BlockingQueue 来简化并发控制。

使用 wait() 和 notify()

这是最原始、最底层的方式,通过 Object 类自带的监视器方法来实现线程间通信。

import java.util.LinkedList;
import java.util.Queue;

public class WaitNotifyExample {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int CAPACITY = 5;

    public void produce() throws InterruptedException {
        synchronized (queue) {
            while (queue.size() == CAPACITY) {
                System.out.println("队列已满,生产者等待...");
                queue.wait();
            }
            int value = (int) (Math.random() * 100);
            queue.offer(value);
            System.out.println("生产者生产: " + value);
            queue.notifyAll(); // 通知消费者
        }
    }

    public void consume() throws InterruptedException {
        synchronized (queue) {
            while (queue.isEmpty()) {
                System.out.println("队列为空,消费者等待...");
                queue.wait();
            }
            int value = queue.poll();
            System.out.println("消费者消费: " + value);
            queue.notifyAll(); // 通知生产者
        }
    }

    public static void main(String[] args) {
        WaitNotifyExample example = new WaitNotifyExample();
        Runnable producer = () -> {
            try {
                while (true) example.produce();
            } catch (InterruptedException e) { e.printStackTrace(); }
        };
        Runnable consumer = () -> {
            try {
                while (true) example.consume();
            } catch (InterruptedException e) { e.printStackTrace(); }
        };
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

特点

灵活、底层,完全可控,更能体现一个程序员的基本功和专业性,同时可以炫技。

但容易写出 Bug,容易翻车,对程序员的编码能力和经验要求高,比如忘记 notifyAll()、死锁、虚假唤醒等。

更适合 教学、理解线程通信原理

使用 BlockingQueue

BlockingQueuejava.util.concurrent 包提供的并发容器,内部实现了 自动阻塞和唤醒机制。对新手更加优好,简单两个api take()put()就能完成上面的代码架子。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {
    private final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);

    public void produce() throws InterruptedException {
        int value = (int) (Math.random() * 100);
        queue.put(value); // 队列满时自动阻塞
        System.out.println("生产者生产: " + value);
    }

    public void consume() throws InterruptedException {
        int value = queue.take(); // 队列空时自动阻塞
        System.out.println("消费者消费: " + value);
    }

    public static void main(String[] args) {
        BlockingQueueExample example = new BlockingQueueExample();
        Runnable producer = () -> {
            try {
                while (true) example.produce();
            } catch (InterruptedException e) { e.printStackTrace(); }
        };
        Runnable consumer = () -> {
            try {
                while (true) example.consume();
            } catch (InterruptedException e) { e.printStackTrace(); }
        };
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

特点

使用了BlockingQueue的ArrayBlockingQueue实现

ArrayBlockingQueue内部基于 ReentrantLock + Condition 实现。

当队列满时,put() 会阻塞;当队列空时,take() 会阻塞,。

自动处理线程间的等待与唤醒,避免手动管理 wait()/notify()

使用更加简单,让程序员能够更加便捷的开发生产者消费者模式的代码

BlockingQueue

BlockingQueue 是一个接口,它的实现类(如 ArrayBlockingQueueLinkedBlockingQueue)底层依赖:

ReentrantLock:保证队列操作的互斥。

Condition 条件变量

notFull → 队列满时,生产者等待;

notEmpty → 队列空时,消费者等待。

队列的操作都是通过ReentrantLock进行加解锁来控制并发

可以看下ArrayBlockingQueue的实现

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

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() 时,如果队列为空:
→ 调用 notEmpty.await() 阻塞,直到有生产者放入数据。
消费或生产完成后,会 signal() 唤醒对应的等待线程。

BlockingQueue常见实现类

实现类特点使用场景
ArrayBlockingQueue基于数组,有界,FIFO,容量固定固定任务队列,如线程池任务缓存
LinkedBlockingQueue基于链表,容量可设,吞吐量高生产快/消费慢的场景,如日志处理
PriorityBlockingQueue支持优先级排序,无界定时任务、优先级队列
DelayQueue元素到期才能取出,基于优先级队列定时任务、订单超时、缓存过期
SynchronousQueue不存储元素,必须直接交付常用于 Executors.newCachedThreadPool()
LinkedTransferQueue高并发无界队列,支持直接传递高频任务传递、消息处理

BlockingQueue常用方法区别

我们已ArrayBlockQueue具体实现为例子

一般我们使用take()和put(),这两个具有阻塞性,使用的是lock.lockInterruptibly();来控制

offer()和poll 使用的是lock.lock();, 拿不到锁资源就返回成功或者失败

add()和无参数的remove()方法,是继承AbstractQueue,错误的情况会抛出异常

具体对比如下表

分类方法队列满/空时行为是否阻塞返回值/异常
插入元素add(e)满时抛 IllegalStateException成功 true,失败抛异常
offer(e)满时返回 falsetrue/false
offer(e, timeout, unit)满时等待指定时间,超时返回 false⏳(有限阻塞)true/false
put(e)满时一直阻塞,直到有空间无返回值
获取元素remove()空时抛 NoSuchElementException元素 / 异常
poll()空时返回 null元素 / null
poll(timeout, unit)空时等待指定时间,超时返回 null有限阻塞元素 / null
take()空时一直阻塞,直到有元素元素
查看队头element()空时抛 NoSuchElementException元素 / 异常
peek()空时返回 null元素 / null

日常开发中的应用场景

在实际开发中,BlockingQueue 使用非常普遍,尤其是 异步任务处理、消息队列、线程池 中。

日志异步写入

BlockingQueue<String> logQueue = new LinkedBlockingQueue<>(1000);

// 生产日志
public void log(String msg) throws InterruptedException {
    logQueue.put(msg);
}

// 消费日志(写文件/写数据库)
public void startLogWorker() {
    new Thread(() -> {
        try {
            while (true) {
                String msg = logQueue.take();
                System.out.println("写日志: " + msg);
            }
        } catch (InterruptedException ignored) {}
    }).start();
}

避免主线程阻塞,日志异步落盘。

订单处理系统

  • 生产者:接收用户订单请求,放入 BlockingQueue
  • 消费者:多个工作线程从队列取订单,进行支付/库存/通知等操作。
BlockingQueue<Order> orderQueue = new LinkedBlockingQueue<>(500);

public void submitOrder(Order order) throws InterruptedException {
    orderQueue.put(order);
}

public void startOrderWorkers() {
    for (int i = 0; i < 5; i++) {
        new Thread(() -> {
            try {
                while (true) {
                    Order order = orderQueue.take();
                    processOrder(order);
                }
            } catch (InterruptedException ignored) {}
        }).start();
    }
}

实现了 异步解耦,同时支持 多消费者并行处理

对比总结

特性wait()/notify()BlockingQueue
复杂度高,需要手动控制低,内置阻塞机制
安全性容易遗漏信号、死锁线程安全,内部保证
可读性代码冗长,难维护简洁清晰,易扩展
使用场景学习、定制化同步实际开发首选

结论

学习阶段:掌握 wait()/notify(),有助于理解底层线程通信原理,基础不牢,地动山摇。

实际开发:推荐使用 BlockingQueue,避免重复造轮子,更安全高效。

根据场景选择不同实现类(ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue 等),能很好地解决日常业务问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ytadpole

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值