生产者和消费者问题是多线程编程中的一个经典问题,用于描述生产者线程和消费者线程之间的协作关系。生产者线程负责生成数据并将其放入缓冲区,消费者线程则从缓冲区中取出数据进行处理。为了确保线程安全和避免数据丢失或线程阻塞,需要使用同步机制来协调生产者和消费者的行为。
以下是使用 Java 实现的生产者和消费者问题的代码,并附有详细注释。
代码实现
package demo;
import java.util.LinkedList;
import java.util.Queue;
public class ProducerAndConsumerDemo {
public static void main(String[] args) {
Buffer buffer = new Buffer(); // 创建一个共享缓冲区
Consumer consumer = new Consumer(buffer); // 创建消费者线程
Producer producer = new Producer(buffer); // 创建生产者线程
consumer.start(); // 启动消费者线程
producer.start(); // 启动生产者线程
}
}
class Producer extends Thread {
private Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
buffer.add(i); // 生产者向缓冲区添加数据
Thread.sleep(500); // 模拟生产时间
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Consumer extends Thread {
private Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
int val = 0;
try {
val = buffer.pull(); // 消费者从缓冲区取出数据
Thread.sleep(1000); // 模拟消费时间
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Buffer {
private Queue<Integer> queue = new LinkedList<>(); // 使用队列作为缓冲区
private int maxSize = 5; // 缓冲区的最大容量
// 生产者向缓冲区添加数据
public synchronized void add(int i) throws InterruptedException {
while (queue.size() >= maxSize) { // 如果缓冲区已满
wait(); // 阻塞生产者线程,等待消费者消费数据
}
queue.add(i); // 将数据添加到缓冲区
System.out.println("生产者生产:" + i); // 打印生产信息
notifyAll(); // 通知消费者线程
}
// 消费者从缓冲区取出数据
public synchronized int pull() throws InterruptedException {
while (queue.isEmpty()) { // 如果缓冲区为空
wait(); // 阻塞消费者线程,等待生产者生产数据
}
int val = queue.poll(); // 从缓冲区取出数据
System.out.println("消费者消费:" + val); // 打印消费信息
notifyAll(); // 通知生产者线程
return val; // 返回取出的数据
}
}
代码解析
1. 主类 ProducerAndConsumerDemo
-
创建一个共享的缓冲区
Buffer
。 -
创建生产者线程
Producer
和消费者线程Consumer
,并将共享缓冲区传递给它们。 -
启动生产者和消费者线程。
2. 生产者类 Producer
-
继承自
Thread
类,重写run
方法。 -
在
run
方法中,生产者线程循环生成数据,并调用Buffer
的add
方法将数据放入缓冲区。 -
每次生产后,线程休眠 500 毫秒,模拟生产时间。
3. 消费者类 Consumer
-
继承自
Thread
类,重写run
方法。 -
在
run
方法中,消费者线程循环从缓冲区取出数据,并调用Buffer
的pull
方法。 -
每次消费后,线程休眠 1000 毫秒,模拟消费时间。
4. 缓冲区类 Buffer
-
使用
LinkedList
实现一个队列作为缓冲区。 -
缓冲区的最大容量为 5。
-
add
方法:-
如果缓冲区已满,调用
wait()
阻塞生产者线程,直到有空间可用。 -
将数据添加到缓冲区,并打印生产信息。
-
调用
notifyAll()
通知消费者线程。
-
-
pull
方法:-
如果缓冲区为空,调用
wait()
阻塞消费者线程,直到有数据可用。 -
从缓冲区取出数据,并打印消费信息。
-
调用
notifyAll()
通知生产者线程。 -
返回取出的数据。
-
生产者和消费者问题的关键点
-
线程安全:通过
synchronized
方法确保对缓冲区的操作是线程安全的。 -
同步机制:
-
使用
wait()
阻塞线程,等待条件满足。 -
使用
notifyAll()
唤醒等待的线程。
-
-
互斥访问:生产者和消费者不能同时对缓冲区进行操作,通过
synchronized
方法实现互斥。
总结
生产者和消费者问题是多线程编程中的一个经典问题,通过使用 wait()
和 notifyAll()
方法,可以有效地协调生产者和消费者的行为,确保线程安全和数据一致性。在实际开发中,也可以使用 BlockingQueue
等更高级的并发工具来简化实现。