Java 生产者-消费者模式实现
Java 生产者-消费者模式实现
模式概述
生产者-消费者模式是经典多线程设计模式,用于协调生产者(生成数据)和消费者(处理数据)的协作。核心目标是通过共享缓冲区传递数据,避免数据竞争和资源浪费,实现高效并发。
核心组件
- 生产者:生成数据并写入共享缓冲区。
- 消费者:从共享缓冲区读取数据并处理。
- 共享缓冲区:存储数据的中间容器(通常为队列),有固定容量限制。
- 同步机制:确保缓冲区操作线程安全,常用锁+条件变量、阻塞队列等。
实现方式一:Lock + Condition(自定义缓冲区)
通过 ReentrantLock 保证缓冲区操作的原子性,结合两个 Condition(notEmpty、notFull)实现线程间精准通信,解决缓冲区空/满时的线程等待问题。
实现逻辑
- 缓冲区为先进先出(FIFO)队列,容量固定。
- 生产者写入数据时,若缓冲区满则等待
notFull条件(消费者取数据后唤醒)。 - 消费者读取数据时,若缓冲区空则等待
notEmpty条件(生产者写数据后唤醒)。
代码示例
package com.deadlock;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Jing61
*/
public class QueueTask {
private static Buffer buffer = new Buffer();
/**
* 存储的工作实例
*/
private static class Task {
private String id;
public Task() {
// 生成随机ID
this.id = UUID.randomUUID().toString();
}
@Override
public String toString() {
return "Task [id=" + id + "]";
}
}
/**
* 缓冲区实际上是一个队列,用于存放任务
*/
private static class Buffer {
private static final int CAPACITY = 10;
private Queue<Task> queue = new LinkedList<>();
private Lock lock = new ReentrantLock(true); // 等待时间越久的线程越先被唤醒
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
/**
* 生产者
* @param task 任务
*/
public void write (Task task) {
lock.lock();
try {
while(queue.size() == CAPACITY) {
System.out.println("缓冲区已满,等待 notFUll");
notFull.await();
}
// 队列未满,将任务写入队列
queue.offer(task);
System.out.println("写入任务:" + task.toString());
// 队列不为空,唤醒 notEmpty 发生
notEmpty.signalAll();
} catch (InterruptedException e) {
System.out.println("等待被中断");
} finally {
lock.unlock();
}
}
/**
* 消费者
* @return 缓冲区中的任务
*/
public Task read () {
Task task = null;
lock.lock();
try {
while(queue.isEmpty()) {
System.out.println("缓冲区已空,等待 notEmpty");
notEmpty.await();
}
// 队列非空,从队列中取出任务
task = queue.poll();
System.out.println("读取任务:" + task.toString());
// 队列非空,唤醒 notFull 发生
notFull.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
return task;
}
}
/**
* 生产者
*/
private static class Producer implements Runnable {
@Override
public void run() {
while(true) {
Task task = new Task();
buffer.write(task);
try {
Thread.sleep(500); // 生产者耗时
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 消费者
*/
private static class Consumer implements Runnable {
@Override
public void run() {
while(true) {
Task task = buffer.read();
try {
Thread.sleep(700); // 消费者耗时
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
var pool = Executors.newFixedThreadPool(2);
pool.execute(new Producer());
pool.execute(new Consumer());
pool.shutdown();
}
}
实现方式二:BlockingQueue(阻塞队列,推荐)
Java 并发包提供 BlockingQueue 接口,其实现类(如 ArrayBlockingQueue、LinkedBlockingQueue)内置阻塞机制,无需手动处理锁和条件变量,简化开发。
阻塞队列核心特性
- 当队列满时,
put()方法会阻塞生产者线程,直到队列有空闲空间。 - 当队列空时,
take()方法会阻塞消费者线程,直到队列有数据。 - 内置线程安全机制,无需额外同步。
常用实现类
ArrayBlockingQueue:基于数组的有界阻塞队列,容量固定。LinkedBlockingQueue:基于链表的阻塞队列,默认无界(可指定容量)。PriorityBlockingQueue:基于优先级的无界阻塞队列,元素按优先级排序。
代码示例
package com.deadlock;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* java集合框架提供了ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue来支持阻塞队列
* 阻塞队列:试图向满队列添加元素或者从空队列删除元素会导致线程阻塞
* @author Jing61
*/
public class ProducerConsumerUsingBlockingQueue {
private static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new Producer());
executor.execute(new Consumer());
executor.shutdown();
}
private static class Producer implements Runnable{
@Override
public void run() {
try {
int value = 1;
while(true) {
// offer 添加数据, 添加成功返回true,添加失败返回false,但不阻塞
// add 添加数据, 添加成功返回true,添加失败抛出异常,不阻塞
// put 添加数据, 无返回值,添加失败阻塞
System.out.println("生产者生产数据:" + value);
queue.put(value++);
Thread.sleep((long)(Math.random() * 5000));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class Consumer implements Runnable{
@Override
public void run() {
try {
while(true) {
// poll 获取数据, 获取成功返回数据,获取失败返回null,不阻塞
// take 获取数据, 获取成功返回数据,获取失败阻塞
System.out.println("消费者消费数据:" + queue.take());
Thread.sleep((long)(Math.random() * 200));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
阻塞队列关键方法对比
| 方法 | 队列满时行为 | 队列空时行为 | 适用场景 |
|---|---|---|---|
add(E e) |
抛出异常 | 添加成功 | 需快速失败的场景 |
offer(E e) |
返回 false |
添加成功 | 不希望阻塞的场景 |
put(E e) |
阻塞线程 | 添加成功 | 生产者-消费者模式 |
take() |
成功返回数据 | 阻塞线程 | 生产者-消费者模式 |
poll() |
成功返回数据 | 返回null |
不希望阻塞的场景 |
两种实现方式对比
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| Lock + Condition | 灵活性高,可自定义条件逻辑 | 代码复杂,需手动管理锁和条件 |
| BlockingQueue | 代码简洁,无需手动同步 | 灵活性较低,依赖队列实现 |

浙公网安备 33010602011771号