// BinaryTreePanel_Part1.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
class BinaryTreePanel extends JPanel {
ProcessNode root;
private ResourceManager resourceManager;
private Random random;
int processId;
private Font chineseFont;
private Timer timeSliceTimer;
public BinaryTreePanel() {
this.root = null;
this.resourceManager = new ResourceManager(3);
this.random = new Random();
this.processId = 0;
chineseFont = createChineseFont();
setBackground(Color.WHITE);
setLayout(new BorderLayout());
timeSliceTimer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
performTimeSliceCountdown();
}
});
timeSliceTimer.start();
}
private Font createChineseFont() {
String[] fontNames = {
"Microsoft YaHei", "SimHei", "SimSun", "KaiTi",
"Arial Unicode MS", "Dialog", "SansSerif"
};
for (String fontName : fontNames) {
Font font = new Font(fontName, Font.PLAIN, 12);
if (font.canDisplay(' ')) {
return font;
}
}
return new Font("Dialog", Font.PLAIN, 12);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawResourceAndQueueInfo(g2d);
if (root != null) {
drawTree(g2d, root);
}
drawQueueVisualization(g2d);
}
private void drawResourceAndQueueInfo(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.setFont(chineseFont.deriveFont(Font.BOLD, 16));
g2d.drawString("I/O Դ1: " + resourceManager.getAvailableResources(), 20, 30);
g2d.drawString("I/O Դ2: " + resourceManager.getAvailableResource2(), 20, 55);
g2d.drawString(" CPU: " + resourceManager.getAvailableCPU(), 20, 80);
g2d.setColor(Color.MAGENTA);
g2d.drawString("ʱ Ƭ: " + resourceManager.getTimeSlice(), 20, 105);
g2d.setFont(chineseFont.deriveFont(Font.BOLD, 14));
g2d.setColor(Color.BLUE);
g2d.drawString(" : " + resourceManager.getReadyCount(), 200, 30);
g2d.setColor(Color.GREEN);
g2d.drawString(" ж : " + resourceManager.getRunningCount(), 350, 30);
g2d.setColor(Color.RED);
g2d.drawString(" 1 : " + resourceManager.getBlocked1Count(), 500, 30);
g2d.setColor(Color.ORANGE);
g2d.drawString(" 2 : " + resourceManager.getBlocked2Count(), 650, 30);
g2d.setColor(Color.BLACK);
g2d.drawString(" ܽ : " + (root != null ? countNodes(root) : 0), 800, 30);
g2d.setColor(Color.RED);
g2d.drawString("ʱ Ƭ Զ ʱ: ON", 20, 130);
g2d.setFont(chineseFont.deriveFont(Font.BOLD, 12));
g2d.setColor(Color.MAGENTA);
g2d.drawString(" ɫ: Դ", 20, 155);
}
private void drawTree(Graphics2D g2d, ProcessNode node) {
if (node == null) return;
if (node.left != null) {
g2d.setColor(Color.GRAY);
g2d.drawLine(node.x, node.y, node.left.x, node.left.y);
drawTree(g2d, node.left);
}
if (node.right != null) {
g2d.setColor(Color.GRAY);
g2d.drawLine(node.x, node.y, node.right.x, node.right.y);
drawTree(g2d, node.right);
}
Color nodeColor;
if (node.status.equals("RUNNING")) {
if (node.hasResource2 && node.resources > 0) {
nodeColor = Color.MAGENTA;
} else {
nodeColor = Color.GREEN;
}
} else if (node.status.equals("BLOCKED_RESOURCE2")) {
nodeColor = Color.ORANGE;
} else if (node.status.equals("BLOCKED_RESOURCE1")) {
nodeColor = Color.RED;
} else {
if (node.hasResource2 && node.resources > 0) {
nodeColor = Color.MAGENTA;
} else {
nodeColor = Color.BLUE;
}
}
g2d.setColor(nodeColor);
g2d.fillOval(node.x - 25, node.y - 25, 50, 50);
g2d.setColor(Color.WHITE);
g2d.setFont(chineseFont.deriveFont(Font.BOLD, 12));
String nodeInfo = node.name;
FontMetrics fm = g2d.getFontMetrics();
int textWidth = fm.stringWidth(nodeInfo);
g2d.drawString(nodeInfo, node.x - textWidth / 2, node.y - 8);
g2d.setFont(chineseFont.deriveFont(Font.PLAIN, 10));
String statusInfo = node.status;
if (node.status.equals("BLOCKED_RESOURCE1")) {
statusInfo = "BLOCKED(R1)";
} else if (node.status.equals("BLOCKED_RESOURCE2")) {
statusInfo = "BLOCKED(R2)";
}
statusInfo += "(" + node.resources + ")";
if (node.hasResource2) {
statusInfo += "+R2";
}
textWidth = fm.stringWidth(statusInfo);
g2d.drawString(statusInfo, node.x - textWidth / 2, node.y + 5);
String idInfo = "ID:" + node.id;
if (node.status.equals("RUNNING") && node.remainingTime > 0) {
idInfo += " T:" + node.remainingTime;
}
textWidth = fm.stringWidth(idInfo);
g2d.drawString(idInfo, node.x - textWidth / 2, node.y + 18);
}
private void drawQueueVisualization(Graphics2D g2d) {
int panelHeight = getHeight();
int queueStartY = panelHeight - 220;
int boxHeight = 30;
int boxWidth = 80;
int spacing = 5;
g2d.setColor(Color.BLUE);
g2d.setFont(chineseFont.deriveFont(Font.BOLD, 14));
g2d.drawString(" (" + resourceManager.getReadyCount() + "):", 50, queueStartY - 10);
int xPos = 50;
int queueIndex = 1;
for (ProcessNode process : resourceManager.getReadyQueue()) {
if (process.hasResource2 && process.resources > 0) {
g2d.setColor(Color.MAGENTA);
} else {
g2d.setColor(Color.BLUE);
}
g2d.fillRect(xPos, queueStartY, boxWidth, boxHeight);
g2d.setColor(Color.WHITE);
g2d.drawString(process.name, xPos + 5, queueStartY + 15);
String resourceInfo = "R1:" + process.resources;
if (process.hasResource2) {
resourceInfo += "+R2";
}
g2d.drawString(resourceInfo, xPos + 5, queueStartY + 28);
g2d.setColor(Color.YELLOW);
g2d.drawString("" + queueIndex, xPos + boxWidth - 15, queueStartY + 15);
xPos += boxWidth + spacing;
queueIndex++;
}
g2d.setColor(Color.GREEN);
g2d.drawString(" ж (" + resourceManager.getRunningCount() + "):", 50, queueStartY + 50);
xPos = 50;
for (ProcessNode process : resourceManager.getRunningQueue()) {
if (process.hasResource2 && process.resources > 0) {
g2d.setColor(Color.MAGENTA);
} else {
g2d.setColor(Color.GREEN);
}
g2d.fillRect(xPos, queueStartY + 60, boxWidth, boxHeight);
g2d.setColor(Color.WHITE);
g2d.drawString(process.name, xPos + 5, queueStartY + 75);
g2d.drawString("T:" + process.remainingTime, xPos + 5, queueStartY + 88);
xPos += boxWidth + spacing;
}
g2d.setColor(Color.RED);
g2d.drawString(" һ (" + resourceManager.getBlocked1Count() + "):", 50, queueStartY + 110);
xPos = 50;
for (ProcessNode process : resourceManager.getBlockedQueue1()) {
g2d.setColor(Color.RED);
g2d.fillRect(xPos, queueStartY + 120, boxWidth, boxHeight);
g2d.setColor(Color.WHITE);
g2d.drawString(process.name, xPos + 15, queueStartY + 140);
xPos += boxWidth + spacing;
}
g2d.setColor(Color.ORANGE);
g2d.drawString(" ڶ (" + resourceManager.getBlocked2Count() + "):", 50, queueStartY + 170);
xPos = 50;
for (ProcessNode process : resourceManager.getBlockedQueue2()) {
g2d.setColor(Color.ORANGE);
g2d.fillRect(xPos, queueStartY + 180, boxWidth, boxHeight);
g2d.setColor(Color.WHITE);
g2d.drawString(process.name, xPos + 15, queueStartY + 200);
xPos += boxWidth + spacing;
}
}
private int countNodes(ProcessNode node) {
if (node == null) return 0;
return 1 + countNodes(node.left) + countNodes(node.right);
}
private void calculatePositions() {
if (root == null) return;
int levelHeight = 80;
int startX = getWidth() / 2;
int startY = 120;
calculatePositionRecursive(root, startX, startY, Math.max(200, getWidth() / 4), levelHeight, 0);
}
private void calculatePositionRecursive(ProcessNode node, int x, int y, int offsetX, int levelHeight, int level) {
if (node == null) return;
node.x = x;
node.y = y;
if (node.left != null) {
calculatePositionRecursive(node.left, x - offsetX, y + levelHeight, offsetX / 2, levelHeight, level + 1);
}
if (node.right != null) {
calculatePositionRecursive(node.right, x + offsetX, y + levelHeight, offsetX / 2, levelHeight, level + 1);
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
}将改代码按照一、实验目的
理解操作系统的进程管理机制:通过可视化方式展示进程状态转换、资源分配与调度过程。
掌握PV操作原理:模拟信号量机制,实现进程的同步与互斥。
学习进程调度算法:实现时间片轮转(RR)调度算法,理解多队列管理。
提升面向对象编程能力:使用Java Swing开发图形界面,实践模块化设计思想。
培养系统分析与问题解决能力:设计并实现一个完整的进程管理系统。
二、实验环境
项目 说明
操作系统 Windows 10/11、macOS或Linux
开发语言 Java
JDK版本 JDK 8及以上
GUI框架 Java Swing
开发工具 IntelliJ IDEA / Eclipse
内存要求 推荐2GB以上
三、系统设计
3.1 系统架构
系统采用三层架构设计:
3.2 核心类设计
3.2.1 ProcessNode(进程节点类)
表示二叉树中的进程节点,包含进程的所有状态信息。
class ProcessNode {
// 基本属性
int id; // 进程唯一标识
String name; // 进程名(P0, P1, ...)
ProcessNode left, right; // 二叉树左右子节点
ProcessNode parent; // 父节点(便于删除操作)
// 显示属性
int x, y; // 绘制坐标
Color color; // 节点颜色
// 状态属性
String status; // 进程状态
int resources; // 持有的第一种资源数量
boolean hasResource2; // 是否持有第二种资源
int remainingTime; // 剩余时间片
String blockedReason; // 阻塞原因
boolean needsTwoResources; // 需要两类资源标志
}
状态说明:
READY:就绪状态(蓝色),持有第一种资源,等待CPU分配。
RUNNING:运行状态(绿色/紫色),正在CPU上执行。
BLOCKED_RESOURCE1:第一阻塞状态(红色),因第一种I/O资源不足而阻塞。
BLOCKED_RESOURCE2:第二阻塞状态(橙色),因第二种I/O资源不足而阻塞。
3.2.2 ResourceManager(资源管理器类)
负责所有资源管理和进程调度,实现信号量机制。
class ResourceManager {
// 资源定义
private int availableResources; // 第一种I/O资源(初始3个)
private int availableResource2; // 第二种I/O资源(初始1个)
private int availableCPU; // 可用CPU数量(初始1个)
// 队列管理
private Queue<ProcessNode> blockedQueue1; // 第一阻塞队列
private Queue<ProcessNode> blockedQueue2; // 第二阻塞队列
private LinkedList<ProcessNode> readyQueue; // 就绪队列(FCFS)
private List<ProcessNode> runningQueue; // 运行队列
// 调度参数
private int timeSlice; // 时间片大小(默认3)
}
3.2.3 BinaryTreePanel(二叉树面板类)
主显示面板,负责进程树的绘制和用户交互。
3.2.4 ProcessBinaryTreeVisualization(主界面类)
应用程序主窗口,集成所有组件。
3.3 数据结构设计
3.3.1 二叉树结构
根节点:P0进程,不参与资源分配。
插入规则:随机选择左子树或右子树插入新节点。
删除规则:删除节点时保留其子节点,重新连接到父节点。
3.3.2 四队列管理
就绪队列(READY):持有第一种资源,等待CPU。
运行队列(RUNNING):正在执行的进程(最多1个)。
第一阻塞队列(BLOCKED_RESOURCE1):因第一种资源不足阻塞。
第二阻塞队列(BLOCKED_RESOURCE2):因第二种资源不足阻塞。
3.4 算法设计
3.4.1 进程添加算法
输入:新进程节点process
输出:是否成功添加
if process.id == 0:
return true // P0不参与资源分配
if availableResources > 0:
availableResources--
readyQueue.add(process)
process.status = "READY"
process.resources = 1
checkReadyToRunning() // 检查是否可以运行
else:
blockedQueue1.add(process)
process.status = "BLOCKED_RESOURCE1"
return true
3.4.2 PV操作算法
procedure performPVOperation():
// 优先处理第二阻塞队列(需要两种资源)
while availableResources > 0 and availableResource2 > 0 and blockedQueue2非空:
process = blockedQueue2.poll()
readyQueue.add(process)
availableResources--
availableResource2--
process.status = "READY"
process.hasResource2 = true
process.color = Color.MAGENTA
// 然后处理第一阻塞队列(只需要第一种资源)
while availableResources > 0 and blockedQueue1非空:
process = blockedQueue1.poll()
readyQueue.add(process)
availableResources--
process.status = "READY"
process.resources = 1
checkReadyToRunning() // 检查是否有进程可以运行
3.4.3 时间片调度算法
procedure performTimeSliceCountdown():
if runningQueue非空:
runningProcess = runningQueue.get(0)
runningProcess.remainingTime--
if runningProcess.remainingTime <= 0:
// 时间片用完
runningQueue.remove(runningProcess)
readyQueue.add(runningProcess) // 移回就绪队列尾部
runningProcess.status = "READY"
availableCPU++
// 检查是否有新的进程可以运行
checkReadyToRunning()
3.4.4 第二种资源申请算法
function requestResource2(process):
if availableResource2 > 0:
// 申请成功
availableResource2--
process.hasResource2 = true
process.color = Color.MAGENTA
return true
else:
// 申请失败,进入第二阻塞队列
runningQueue.remove(process)
blockedQueue2.add(process)
process.status = "BLOCKED_RESOURCE2"
process.color = Color.ORANGE
// 释放持有的第一种资源
availableResources += process.resources
process.resources = 0
// 释放CPU
availableCPU++
// 检查第一阻塞队列
checkBlocked1ToReady()
return false
四、系统实现
4.1 界面设计
4.1.1 主界面布局
4.1.2 颜色编码系统
颜色 含义 状态 资源持有情况
蓝色 就绪 READY 持有第一种资源
绿色 运行 RUNNING 持有第一种资源
紫色 双资源 RUNNING/READY 持有两种资源
红色 第一阻塞 BLOCKED_RESOURCE1 无资源
橙色 第二阻塞 BLOCKED_RESOURCE2 无资源
4.2 关键功能实现
4.2.1 进程树绘制
坐标计算:递归计算每个节点的(x, y)坐标。
节点绘制:绘制圆形节点,内部显示进程信息。
连接线绘制:绘制父子节点间的连接线。
4.2.2 队列可视化
位置固定:在面板底部显示四个队列。
动态更新:队列内容随进程状态变化实时更新。
颜色区分:不同队列使用不同颜色边框。
4.2.3 自动时间片倒计时
// 每秒触发一次时间片倒计时
timeSliceTimer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
performTimeSliceCountdown();
}
});
timeSliceTimer.start();
4.3 用户交互功能
4.3.1 控制按钮功能
按钮名称 功能说明 触发事件
添加单个进程 插入一个新进程节点 insertNode()
随机生成5个进程 批量添加5个进程 generateRandomNodes(5)
删除随机节点 随机删除一个节点 deleteRandomNode()
删除指定节点 用户选择节点删除 deleteSpecificNode()
执行PV操作 执行一次PV操作 executePVOperation()
申请第二种资源 运行进程申请资源2 requestResource2()
手动时间片轮换 强制时间片轮换 executeTimeSliceRotation()
设置时间片大小 修改时间片长度 setTimeSlice()
随机操作 随机执行一个操作 performRandomOperation()
开始/停止自动操作 自动模式切换 toggleAutoOperation()
清空所有节点 重置系统 clearAllNodes()
4.3.2 自动操作模式
启动:每2秒随机执行一个操作(PV、申请资源2、时间片轮换、随机操作)。
停止:恢复手动操作模式。
禁用按钮:自动模式下禁用大部分手动操作按钮。
五、实验结果与分析
5.1 功能测试
测试1:基本功能测试
测试项目 操作步骤 预期结果 实际结果 通过
添加进程 点击"添加单个进程" 进程树新增节点,资源减少 符合预期 ✓
资源分配 连续添加4个进程 前3个就绪,第4个阻塞 符合预期 ✓
PV操作 删除进程后执行PV 阻塞进程进入就绪队列 符合预期 ✓
时间片倒计时 等待3秒 运行进程移回就绪队列 符合预期 ✓
测试2:边界条件测试
测试场景 测试方法 结果 说明
空树操作 清空后执行各种操作 显示提示信息 正确处理
资源耗尽 添加大量进程 后续进程进入阻塞队列 队列管理正常
时间片边界 设置时间片为1 快速轮换 调度正常
节点删除 删除根节点 树结构保持完整 删除逻辑正确
测试3:并发操作测试
测试组合 操作序列 结果 稳定性
混合操作1 添加→PV→删除→申请资源2 状态正确转换 稳定
混合操作2 自动模式下连续操作 无崩溃,响应正常 稳定
压力测试 快速连续点击按钮 界面无闪烁,响应及时 良好
5.2 性能分析
响应时间测试
操作类型 10个节点 30个节点 50个节点
添加节点 10ms 15ms 20ms
删除节点 12ms 18ms 25ms
PV操作 15ms 22ms 30ms
界面刷新 20ms 30ms 45ms
内存使用分析
节点数量 内存占用 CPU使用率 备注
10个节点 ~45MB 2-5% 基础占用
30个节点 ~55MB 3-7% 正常范围
50个节点 ~65MB 5-10% 可接受
100个节点 ~85MB 8-15% 建议不超过50节点
5.3 算法正确性验证
PV操作正确性
通过以下场景验证:
场景1:释放第一种资源后,第一阻塞队列进程正确进入就绪队列。
场景2:释放第二种资源后,第二阻塞队列进程正确进入就绪队列。
场景3:同时释放两种资源,第二阻塞队列优先于第一阻塞队列。
时间片调度正确性
验证点:
时间片自动倒计时功能正常。
时间片用完自动移回就绪队列尾部。
就绪队列头进程正确进入运行队列。
资源管理正确性
验证点:
资源分配符合初始设置(资源1=3,资源2=1,CPU=1)。
资源释放后正确回收。
资源申请失败时进程状态正确转换。
六、创新点与特色
6.1 技术创新
双阻塞队列设计
区分因不同资源类型阻塞的进程。
实现不同的唤醒条件(第二阻塞队列需要两种资源)。
动态颜色编码系统
五种颜色清晰区分五种进程状态。
紫色特别标识持有两种资源的特殊状态。
混合调度策略
时间片轮转(RR)用于CPU调度。
先来先服务(FCFS)用于就绪队列管理。
优先级调度用于PV操作(第二阻塞队列优先)。
实时可视化
进程树与队列同步显示。
状态变化实时反馈。
6.2 教学价值
直观理解:将抽象的进程状态转换为可视化的颜色和图形。
交互学习:支持手动操作,亲身体验调度过程。
实验验证:可以验证各种调度算法的效果。
错误模拟:可以模拟死锁、资源竞争等场景。
6.3 用户体验
操作简便:按钮布局合理,功能明确。
信息完整:实时显示资源状态、队列长度等关键信息。
提示友好:操作结果通过对话框提示。
容错性强:对非法操作有友好的提示信息。
七、问题与改进
7.1 遇到的问题及解决方案
问题描述 难点分析 解决方案 效果
节点删除后资源释放逻辑复杂 不同状态节点需要不同的资源处理 设计统一的releaseNodeResources()方法 资源释放正确
界面刷新频繁导致闪烁 频繁重绘影响视觉体验 使用双缓冲技术,优化绘制逻辑 显示流畅
中文显示兼容性问题 不同系统字体支持不一致 创建字体检测机制,自动选择可用字体 中文正常显示
自动模式下的并发控制 多线程操作可能导致状态不一致 使用synchronized关键字同步关键方法 线程安全
7.2 系统局限性
扩展性有限:只支持两种资源类型,难以扩展到多种资源。
调度算法单一:仅实现了时间片轮转,缺少其他调度算法。
进程数限制:界面空间有限,大量节点时显示拥挤。
缺乏持久化:关闭程序后数据丢失。
7.3 改进建议
短期改进(1-2周)
增加更多调度算法(优先级调度、多级反馈队列)。
添加进程间通信机制模拟。
实现数据导出功能(保存实验记录)。
中期改进(1-2个月)
支持更多资源类型(可配置)。
添加死锁检测和恢复机制。
实现分布式进程模拟。
长期改进(3-6个月)
开发Web版本,支持多用户协作。
集成机器学习算法,智能优化调度。
构建完整的操作系统教学平台。
八、实验总结
8.1 主要成果
成功实现了完整的进程管理系统:
支持进程的创建、删除、状态转换。
实现了PV操作、时间片调度、资源管理。
开发了直观的可视化界面:
进程树和队列同步显示。
实时状态更新和颜色编码。
构建了交互式实验平台:
支持手动和自动两种操作模式。
提供丰富的操作选项。
验证了操作系统核心概念:
进程状态转换、资源分配、调度算法。
信号量机制、PV操作、时间片轮转。
8.2 技术收获
Java编程能力提升:
熟练使用Swing开发图形界面。
掌握多线程编程和同步机制。
实践面向对象设计原则。
系统设计能力培养:
从需求分析到系统实现的完整流程。
模块化设计和接口定义。
异常处理和用户交互设计。
操作系统知识深化:
深入理解进程管理和调度算法。
掌握资源分配和同步机制。
理解可视化在教学中的价值。
8.3 心得体会
通过本次实验,我深刻体会到:
理论与实践结合的重要性:书本上的操作系统概念通过可视化实现变得直观易懂。
系统设计的复杂性:即使是相对简单的系统,也需要考虑各种边界条件和异常情况。
用户交互的关键性:良好的用户体验需要精心设计界面和操作流程。
持续改进的价值:系统开发是一个不断迭代和完善的过程。
来写一份报告
最新发布