生成者消费者模型是一个经典的并发编程问题。在这个模型中,生成者(生产者)生成数据(在这个例子中是食堂的饭菜),而消费者(学生)消费这些数据。使用 List 集合替换队列,可以在一定程度上简化实现。这里给出一个简单的示例代码,使用 Java 实现学校食堂和学生消费者的模型。
java
import java.util.ArrayList;
import java.util.List;
class Canteen {
private List<String> meals = new ArrayList<>();
private final int capacity = 5; // 食堂餐品的最大容量
public synchronized void produce(String meal) throws InterruptedException {
while (meals.size() == capacity) {
wait(); // 等待消费,满了不能再生产
}
meals.add(meal);
System.out.println("生成者生产了: " + meal);
notifyAll(); // 通知消费者可以消费了
}
public synchronized String consume() throws InterruptedException {
while (meals.isEmpty()) {
wait(); // 等待生产,空了不能消费
}
String meal = meals.remove(0);
System.out.println("消费者消费了: " + meal);
notifyAll(); // 通知生产者可以生产了
return meal;
}
}
class Producer implements Runnable {
private final Canteen canteen;
public Producer(Canteen canteen) {
this.canteen = canteen;
}
@Override
public void run() {
try {
String[] meals = {"米饭", "面条", "三明治", "披萨", "汉堡"};
for (String meal : meals) {
canteen.produce(meal);
Thread.sleep(500); // 模拟生产时间
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
private final Canteen canteen;
public Consumer(Canteen canteen) {
this.canteen = canteen;
}
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
canteen.consume();
Thread.sleep(1000); // 模拟消费时间
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class SchoolCanteen {
public static void main(String[] args) {
Canteen canteen = new Canteen();
Producer producer = new Producer(canteen);
Consumer consumer = new Consumer(canteen);
Thread producerThread = new Thread(producer);
Thread consumerThread = new Thread(consumer);
producerThread.start();
consumerThread.start();
}
}
代码说明:
-
Canteen 类:
- 使用 List<String> 来存储食堂的餐品。
produce
方法用于生产餐品,首先检查是否达到最大容量,如果是则等待,生产后通知消费者。consume
方法用于消费餐品,首先检查是否为空,如果是则等待,消费后通知生产者。
-
Producer 类:
- 代表生产者,不断生产预设的餐品,模拟生产时间。
-
Consumer 类:
- 代表消费者,模拟消费餐品,消费完后等待一段时间。
-
SchoolCanteen 类:
- 创建
Canteen
,Producer
和Consumer
实例,并启动两个线程进行生产和消费。
- 创建
这个代码示例展示了如何使用 List 集合实现生成者消费者模型,同时也使用 Java 的多线程功能来模拟并发的生产和消费过程
volitle 关键字作用:
在 Java 中,volatile
关键字是一个关键字,主要用于保证多线程环境下的共享变量的可见性和禁止指令重排。以下是 volatile
关键字的主要作用:
-
可见性:
- 当一个线程修改了一个
volatile
变量,其他线程立即能够看到这个变量的最新值。这是因为volatile
变量直接从主内存中读取,而不是从线程的工作内存中读取。
- 当一个线程修改了一个
-
禁止指令重排:
- 对于
volatile
变量,编译器和运行时不会对该变量的读写进行指令重排,这保证了对volatile
变量的操作顺序性。这意味着在该变量前面的所有操作在其后面操作之前完成。虽然这并不意味着加锁(synchronized
),但在某些情况下可以用来替代加锁,提高性能。
- 对于
使用场景:
volatile
关键字一般适用于以下场景:
- 状态标志:当一个线程需要停止或中断另一个线程时,常使用
volatile
变量作为状态标志。 - 简单的共享数据:在没有复杂同步操作的情况下,例如计数器、一致性标志等。
例子:
java
class VolatileExample {
private volatile boolean running = true;
public void run() {
while (running) {
// 执行某些操作
}
System.out.println("线程停止运行。");
}
public void stop() {
running = false; // 修改 volatile 变量
}
public static void main(String[] args) throws InterruptedException {
VolatileExample example = new VolatileExample();
Thread thread = new Thread(example::run);
thread.start();
Thread.sleep(1000); // 主线程等待一秒
example.stop(); // 停止线程
thread.join(); // 等待线程结束
}
}
在这个例子中,running
是一个 volatile
变量。当 stop
方法被调用时,running
变量被设置为 false
,其他线程(例如 run
方法中的线程)能够立即看到这个变化,并且停止运行。
注意事项:
volatile
不能替代锁。如果多个线程需要对共享变量进行复杂的操作(如递增、减少等),则应该使用synchronized
或Lock
。volatile
只适用于变量的读写,而不适用于需要组合操作的情况,这可能会引发竞态条件。