基于Java Swing 可视化模拟生产者与消费者问题
文章目录
前言
本文介绍计算机系统进程同步中经典的生产者与消费者问题,同时通过代码在控制台模拟了整个过程。最后基于Java Swing 开发项目,可视化模拟消费者与生产者问题。
一、什么是进程同步?
提到生产者与消费者问题,就不得不提到进程同步,那么进程同步的作用是什么呢?
:简单来说,进程同步让并发执行的多个进程之间按照一定的规则,共享系统资源,使程序的执行具有可再现性。
二、什么是生产者与消费者问题?
生产者-消费者问题是一个著名的进程同步问题。它描述的是:有一群生产者进程在生产产品,并将这些产品提供给消费者进程进行消费。
为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓存区的缓冲池(本项目n = 1),生产者进程将其所生产的产品放入其中一个缓存区中;消费者进程可从一个缓存区中取走产品区消费。
尽管所有的生产者进程与消费者进程都是以异步方式运行的,但它们之间必须保持同步,既不允许消费者进程到一个空缓存区区取产品,也不允许生产者进程向一个已装满产品且尚未被去凑的缓存区中投放产品。
三、Java关键代码演示与讲解
1. 运行演示
创建三个生产者、三个消费者进程。
2. 生产者类(Producer)
代码如下:
private Buffer buffer;
public boolean isPause = false;// 控制生产者状态(生产/暂停)
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
if(!isPause){
// 缓存区商品+1
buffer.produceAGood();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
3. 消费者类(Consumer)
代码如下:
public boolean isPause = false;// 控制消费者状态(消费/暂停)
public Buffer buffer;
@Override
public void run() {
while(true){
try {
Thread.sleep(3000);
if(!isPause){
// 缓存区商品-1
buffer.consumeAGood();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
4. 缓存区类(Buffer)
代码如下:
// 缓存区最大商品数
private static final int MAX = 10;
// 当前缓存区商品的个数
private int GoodNum = 0;
// 判断缓存区是否已经满了
public boolean isFull = false;
// 判断缓存区是否为空
public boolean isEmpty = true;
// 锁
private final Lock lock = new ReentrantLock();
// 缓存区满的条件变量
private final Condition full = lock.newCondition();
// 缓存区空的条件变量
private final Condition empty = lock.newCondition();
// 生产者生产商品
public void produceAGood(){
// 获取锁,同一个时间只能有一个生产者生产商品
lock.lock();
// 如果当前缓存区一直为满
while (isFull){
try {
// 将该进程置为等待
full.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 开始生产
GoodNum += 1;
if(GoodNum == MAX){
isFull = true;
}
// 如果生产之前,缓存区为空
if(isEmpty){
isEmpty = false;
// 唤醒消费者进程
empty.signalAll();
}
System.out.println(Thread.currentThread().getName()+" 生产了第 " + (GoodNum) + " 个商品....");
// 释放锁
lock.unlock();
}
// 消费者消费商品
public void consumeAGood(){
// 获得锁,同一个时间只能有一个消费者消费商品
lock.lock();
// 如果当前缓存区一直为空
while (isEmpty){
try {
// 将该进程置为等待
empty.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 开始消费
GoodNum -= 1;
if(GoodNum == 0){
isEmpty = true;
}
// 如果消费之前缓存区已满
if(isFull){
isFull = false;
// 唤醒生产者进程
full.signalAll();
}
System.out.println(Thread.currentThread().getName()+" 消费了第 " + (GoodNum+1) + " 个商品....");
// 释放锁
lock.unlock();
}
关键代码讲解:
// 创建一个static锁,保证该类不同线程实例能够感知到signalAll()
private final Lock lock = new ReentrantLock();
// 缓存区满的条件变量。await() 与 signalAll() 是基于此实现的。
private final Condition full = lock.newCondition();
// 缓存区空的条件变量
private final Condition empty = lock.newCondition();
// 举例生产者进程讲解 await() 与 signalAll() 的使用。
public void produceAGood(){
// 获取锁,同一个时间只能有一个生产者生产商品
lock.lock();
// 如果当前缓存区一直为满
while (isFull){
// 将该进程置为等待
full.await();
}
// 开始生产
GoodNum += 1;
// 如果生产之前,缓存区为空,则说明可能有消费者进程等待被唤醒。
if(isEmpty){
// 唤醒消费者进程
empty.signalAll();
isEmpty = false;
}
System.out.println(Thread.currentThread().getName()+" 生产了第 " + (GoodNum) + " 个商品....");
// 该生产者完成该次生产,需释放锁
lock.unlock();
}
思路成立,开始实现!
四、界面演示与讲解
1. 界面演示
2. 界面讲解
(1)生产者生产商品的过程抽象为进度条的加载过程,当进度条加载完成,则表示生产者完成一次商品的生产。
Producer 类关键代码如下:
public class Producer implements Runnable{
private int MIN_PROGRESS = 0;
private int MAX_PROGRESS = 100;
private int currentProgress = MIN_PROGRESS;
public boolean isPause = true;
@Override
public void run() {
while(true){
try {
Thread.sleep(produceSpeed);
// 该生产者处于生产状态
if(!isPause){
// 设置进度条对应的值
currentProgress++;
// 当生产者完成一次生产时
if (currentProgress >= MAX_PROGRESS) {
// 缓存区商品+1
buffer.produceAGood();
// 重新开始生产一个新的商品
currentProgress = 0;
}else {
// 采用进度条显示生产商品的过程
myProgressBar.getProgressBar().setValue(currentProgress);
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
(2)消费者消费商品的过程抽象为当Waiter图片(结合上面gif)移动到指定位置时,消费者发起消费一次商品的生产。
Consumer 类关键代码如下:
public class Consumer implements Runnable{
// 控制消费者状态(消费/暂停)
public boolean isPause = true;
public Buffer buffer;
@Override
public void run() {
while(true){
try {
Thread.sleep(consumeSpeed);
if(!isPause){
picPosY--;
// 消费者完成一次消费
if (picPosY <= DEADLINE) {
// 缓存区商品-1
buffer.consumeAGood();
// 重置消费者位置(消费者开启一次新的消费)
picPosY = 550;
}
// 更新消费者的位置
listeners.firePropertyChange(Thread.currentThread().getName(),picPosY+1,picPosY);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
五、项目地址
项目托管在Gitee上,为开源项目,可供学习参考与非商用使用,侵权必究。
模拟生产者与消费者过程
六、总结
通过该项目,对进程同步相关的概念以及同步机制有了进一步的了解,同时锻炼了简单程序的开发能力。