Java多线程生产者消费者模式实战
本文利用Java多线程技术,以“子弹填入枪膛”为场景,详细解析如何通过内置锁(synchronized)、wait() 和 notify() 方法实现生产者与消费者的线程同步。该方案旨在防止缓冲区溢出或为空时引发的错误操作,确保线程安全交互。
问题分析:线程同步的核心
生产者-消费者问题要求两个线程协同工作:一个负责向共享缓冲区放入数据,另一个负责取出。核心难点在于同步控制:
- 当缓冲区已满时,生产者必须暂停生产并等待,直到消费者取走空间;
- 当缓冲区为空时,消费者必须暂停等待,直到生产者放入数据。
在 Java 中,最简单且基础的实现方式是使用对象锁的 wait() 和 notify() 机制。wait() 会使当前线程释放锁并进入休眠,notify() 则随机唤醒一个处于等待状态的线程,使其重新竞争锁。
第1步:定义共享缓冲区(枪膛)
首先需要创建一个代表缓冲区的类 GunClip。该类持有用于存放数据的列表,并利用布尔值 bFull 标记状态。
- 目的:统一管理资源访问,提供安全的放入和获取接口。
- 关键设计:所有读写操作必须包裹在
synchronized块中,保证同一时刻只有一个线程能修改状态。
class GunClip {
private List<Integer> list = new ArrayList<>();
// 标记是否满了,true表示已满,false表示已空
private boolean bFull = false;
// 生产者调用此方法放入子弹
public synchronized void put(List<Integer> bullets) {
// 如果已满,生产者直接等待
if (bFull) {
try {
wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 执行放入操作
this.list = bullets;
bFull = true;
System.out.println("\n机枪开始压入子弹:" + "\n");
for (int j = 0; j < list.size(); j++) {
System.out.println("已压入第" + (j + 1) + "颗子弹");
}
// 放入完成后通知等待的消费者
notify();
}
// 消费者调用此方法取出子弹
public synchronized void get() {
// 如果为空,消费者直接等待
if (!bFull) {
try {
wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 执行取出操作
System.out.println("\n机枪开始射出子弹:" + "\n");
for (int i = list.size(); i > 0; i--) {
int j = list.get(i - 1);
System.out.println("已射出第" + j + "颗子弹");
}
// 清空并重置状态,通知等待的生产者
bFull = false;
notify();
}
}
第2步:实现生产者线程
生产者负责模拟连续装入子弹的过程。它持有对缓冲区 GunClip 的引用,循环创建新的子弹列表并调用缓冲区的 put 方法。
class Producer extends Thread {
private GunClip clip;
private List<Integer> list;
Producer(GunClip clip) {
this.clip = clip;
}
@Override
public void run() {
// 循环生产3轮
for (int i = 0; i < 3; i++) {
list = new ArrayList<>();
// 假设每个弹夹容量为12
for (int j = 0; j < 12; j++) {
list.add(j + 1);
}
clip.put(list);
}
}
}
第3步:实现消费者线程
消费者线程需要不断从缓冲区获取数据。为了演示持续的效果,此处使用 while(true) 死循环调用 get 方法。在实际业务逻辑中,应包含退出机制。
class Consumer extends Thread {
private GunClip clip;
Consumer(GunClip clip) {
this.clip = clip;
}
@Override
public void run() {
while (true) {
clip.get();
}
}
}
第4步:启动测试入口
在主程序中实例化上述类并启动线程。注意类名中的拼写修正:Concumer 应更正为 Consumer,以免引起误解。
public class TestGun {
public static void main(String[] args) {
GunClip clip = new GunClip();
Producer p = new Producer(clip);
Consumer c = new Consumer(clip);
p.start();
c.start();
}
}
完成判断与注意事项
运行上述代码后,控制台将交替输出“机枪开始压入子弹”和“机枪开始射出子弹”的信息。这表明 wait() 和 notify() 已生效,线程在缓冲区满/空时正确挂起,并在条件满足后被唤醒。
在实际开发中,处理多线程同步还需注意以下几点:
- 伪唤醒(Spurious Wakeups):
wait()可能在没有收到notify的情况下返回,因此最佳实践是在if语句外使用while循环检查状态条件。 - 中断异常:捕获
InterruptedException时应妥善处理,通常通过恢复中断状态来保留上游线程的中断信息。 - 锁的粒度:对于复杂的并发需求,推荐使用
java.util.concurrent包下的BlockingQueue或ReentrantLock以获得更灵活的控制。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









