不得不说,利用IDEA的GUI Form对Swing的支持,使得我们可以直接在一个没有父类的(也就是不用继承JFrame)的普通类起步来构建我们的GUI Application。不得不承认,虽然现在IDEA对于Swing的“所见即所得”的拖拉控件可视化做的没有NetBeans好,虽然我们可以直接在NetBeans上直接拖拉控件然后点击相应控件来添加我们的逻辑,但是对于真正的开发人员来说却有一个现象,他们很少使用“拖拉拽”这种“傻瓜式的”添加控件的方式,他们大多都是通过Swing中的布局管理器来组织他们所添加的控件,并调用Swing中布局管理器的相应API和控件本身的API来控制控件的布局。
有过Android开发经验的朋友应该知道,为什么Android的AbsoluteLayout(绝对布局)已经被人抛弃,早已过时。因为这种布局管理器根本没有提供什么“布局管理”的支持,它其实就像Swing中你为某个Container调用“setLayout(null);”一样,它需要我们通过X、Y坐标来控制控件的摆放位置。也许我们在模拟机上试出了漂亮的界面,到了真机测试才发现“这是什么啊”!运行Android应用的手机往往千差万别,因此屏幕分辨率、屏幕大小一般都存在较大差异,使用绝对布局很难兼顾不同屏幕分辨率、大小的问题。由此我们也不难解释上面的现象了。
之所以使用IDEA来开发Swing, 还有一个重要的原因,就是所需代码量极少!你可以先使用NetBeans编写一个带有一个按钮的简单的GUI界面(不管你使用“拖拉拽”还是纯粹手工编写,当然“脱拉拽”会生成“一大坨”额外的布局代码,看着很难受),然后看看我下面使用IDEA编写的代码:
/* * @program: MFQ * @description: * @author: WuchangI * @create: 2018-05-24-08-48 **/ import javax.swing.*; public class Testing { private JButton button; private JPanel panel; public static void main(String[] args) { JFrame frame = new JFrame("Testing"); frame.setContentPane(new Testing().panel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(1000, 800); frame.setVisible(true); } }
好处很明显,也不用多讲。
除此之外,IDEA的.form文件帮我们封装了Swing控件的一些布局信息(自动生成的),有点像Android的.xml布局文件,不像NetBeans那样将业务逻辑和布局显示都混在一起(有点out了),体现了MVC的应用,这样有利于专注我们业务逻辑的实现。
一、效果预览
前面讲了那么多废话,马上使用IDEA来实现这个MFQ(multi-level feedback queues),这里先看看效果:
二、多级反馈队列调度算法简介
无论是在批处理系统还是分时系统中,用户进程数一般都多于处理机数, 这将导致它们互相争夺处理机。另外,系统进程也同样需要使用处理机。这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行。因此,我们需要用到一些调度方式来解决进程互相争夺资源,使得每个进程都很好的使用的处理机。
多级反馈队列调度算法 就是一种CPU处理机调度算法,是目前公认的较好的一种进程调度算法,它能较好的满足各类进程的需要,UNIX操作系统采取的便是这种调度算法。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。具体实现如下:
应设置多个就绪队列,并为各个队列赋予不同的优先级。
第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。
当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n队列中便采取按时间片轮转的方式运行。
仅当第一队列空闲时,调度程序才调度第二队列中的进程运行; 仅当第1~(i-1) 队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
三、需求分析和设计
采用多级反馈队列调度算法进行进程调度的模拟。
- 每个进程对应一个 PCB。在 PCB 中包括进程标识符 pid、进程的状态标识 status、进程优先级 priority、表示进程生命周期的数据项 life(在实际系统中不包括该项)。
- 创建进程时即创建一个 PCB,各个进程的 pid 都是唯一的,pid 是在 1 到 100 范围内的一个整数。
- 可以创建一个下标为 1 到 100 的布尔数组, “假”表示下标对应的进程标识号是空闲的,“真”表示下标对应的进程标识号已分配给某个进程。
- 进程状态 status 的取值为“就绪 ready”或“运行 run”,刚创建时,状态为“ready”。被进程调度程序选中后变为“run”。
- 进程优先级 priority 是 0(最低) 到 49(最高) 范围内的一个随机整数。
- 进程生命周期 life 是 1 到 5 范围内的一个随机整数。
- 初始化时,创建 50 个就绪队列,各就绪队列的进程优先级 priority 分别是 0 到 49。
- 为了模拟用户动态提交任务的过程,要求动态创建进程。进入进程调度循环后,每次按 ctrl+f 即动态创建一个进程,然后将该 PCB 插入就绪队列中。
- 在进程调度循环中,每次选择优先级大的就绪进程来执行。将其状态从就绪变为运行,通过延时一段时间来模拟该进程执行一个时间片 的过程,然后优先级减半,生命周期减一。
- 如果将该运行进程的生命周期不为 0,则重新把它变为就绪状态,插入就绪队列中;否则该进程执行完成,撤消其 PCB。以上为一次进程调度循环。
- 设计图形用户界面 GUI,在窗口中显示该进程和其他所有进程的 PCB 内容。
四、代码实现
1. 进程控制块类
Code:
PCB.java
package com.wuchangi;
/*
* @program: MFQ
* @description: PCB
* @author: WuchangI
* @create: 2018-05-20-22-04
**/
//进程控制块类
public class PCB
{
//进程标识符
private int pid;
//进程状态标识
private String status;
//进程优先级
private int priority;
//进程生命周期
private int life;
public PCB()
{
}
public PCB(int pid, String status, int priority, int life)
{
this.pid = pid;
this.status = status;
this.priority = priority;
this.life = life;
}
public int getPid()
{
return pid;
}
public void setPid(int pid)
{
this.pid = pid;
}
public String getStatus()
{
return status;
}
public void setStatus(String status)
{
this.status = status;
}
public int getPriority()
{
return priority;
}
public void setPriority(int priority)
{
this.priority = priority;
}
public int getLife()
{
return life;
}
public void setLife(int life)
{
this.life = life;
}
}
2. 控制块队列类
Code:
PCBsQueue.java
package com.wuchangi;
/*
* @program: MFQ
* @description: PCBsQueue
* @author: WuchangI
* @create: 2018-05-23-13-49
**/
import java.util.LinkedList;
//控制块队列类
class PCBsQueue
{
//队列优先级
private int priority;
private LinkedList<PCB> queue = new LinkedList<PCB>();
public PCBsQueue(int priority)
{
this.priority = priority;
}
public int getPriority()
{
return priority;
}
public void setPriority(int priority)
{
this.priority = priority;
}
public LinkedList<PCB> getQueue()
{
return queue;
}
public void setQueue(LinkedList<PCB> queue)
{
this.queue = queue;
}
}
3. 多级反馈队列进程调度模拟类
Code:
MFQSimulation.java
package com.wuchangi;
/*
* @program: MFQ
* @description: MFQSimulation
* @author: WuchangI
* @create: 2018-05-20-22-04
**/
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.util.Arrays;
import java.util.LinkedList;
public class MFQSimulation
{
private static JFrame frame = new JFrame("进程调度模拟(多级反馈队列)");
private static JPanel panel = new JPanel();
private static JScrollPane scrollPane = new JScrollPane(panel, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
//菜单组件
private static JMenuBar menuBar = new JMenuBar();
private static JMenu processSettingsMenu = new JMenu("Process Settings");
private static JMenuItem createProcessItem = new JMenuItem("Create A Process");
private static JMenuItem startMFQItem = new JMenuItem("Start Scheduling");
private static JMenuItem stopMFQItem = new JMenuItem("Stop Scheduling");
private static JMenuItem setTimeSliceItem = new JMenuItem("Set Time Slice");
private static JMenuItem exitSystemItem = new JMenuItem("Exit");
private static JMenu helpMenu = new JMenu("Help");
private static JMenuItem aboutItem = new JMenuItem("About");
//设置优先级最高(即49)的队列的时间片大小默认值(单位:秒)
public static double timeSlice = 0.5;
//设置每个队列对应的时间片大小
public static double PCBsQueuesTimeSlice[] = new double[50];
//多级反馈队列
public static PCBsQueue[] PCBsQueues = new PCBsQueue[50];
//记录已经使用的pid
public static int[] pidsUsed = new int[101];
//当前内存中的进程数
public static int currentPCBsNum = 0;
//内存中能够容纳的最大进程数(这里取决于可分配的pid的个数)
public static final int PCBS_MAX_NUM = 100;
//是否停止调度
public static boolean isStopScheduling;
//很短的main函数
public static void main(String[] args)
{
new MFQSimulation().initWindow();
}
//执行窗口初始化
public void initWindow()
{
//设置窗口风格为Windows风格
setWindowsStyle();
//创建菜单栏
processSettingsMenu.add(createProcessItem);
processSettingsMenu.addSeparator();
processSettingsMenu.add(startMFQItem);
processSettingsMenu.addSeparator();
processSettingsMenu.add(stopMFQItem);
processSettingsMenu.addSeparator();
processSettingsMenu.add(setTimeSliceItem);
processSettingsMenu.addSeparator();
processSettingsMenu.add(exitSystemItem);
helpMenu.add(aboutItem);
menuBar.add(processSettingsMenu);
menuBar.add(helpMenu);
frame.setJMenuBar(menuBar);
initMemory();
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
frame.setContentPane(scrollPane);
frame.setSize(800, 700);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
//为控件绑定监听器
setComponentsListeners();
}
//设置Swing的控件显示风格为Windows风格
public static void setWindowsStyle()
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e)
{
e.printStackTrace();
}
}
//初始化相关内存参数
public static void initMemory()
{
currentPCBsNum = 0;
Arrays.fill(pidsUsed, 1, 101, 0);
for(int i = 0; i < PCBsQueues.length; i++)
{
PCBsQueues[i] = new PCBsQueue(i);
}
for(int i = PCBsQueuesTimeSlice.length - 1; i >= 0; i--)
{
//队列优先级每降一级,时间片增加0.1秒
PCBsQueuesTimeSlice[i] = timeSlice;
timeSlice += 0.1;
}
}
//给窗口中所有控件绑定监听器
public static void setComponentsListeners()
{
createProcessItem.setAccelerator(KeyStroke.getKeyStroke('F', InputEvent.CTRL_MASK));
createProcessItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
createProcess();
}
});
startMFQItem.setAccelerator(KeyStroke.getKeyStroke('S', InputEvent.CTRL_MASK));
startMFQItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
startMFQSimulation();
}
});
stopMFQItem.setAccelerator(KeyStroke.getKeyStroke('P', InputEvent.CTRL_MASK));
stopMFQItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
stopMFQSimulation();
}
});
setTimeSliceItem.setAccelerator(KeyStroke.getKeyStroke('T', InputEvent.CTRL_MASK));
setTimeSliceItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setTimeSlice();
}
});
exitSystemItem.setAccelerator(KeyStroke.getKeyStroke('E', InputEvent.CTRL_MASK));
exitSystemItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
});
aboutItem.setAccelerator(KeyStroke.getKeyStroke('A', InputEvent.CTRL_MASK));
aboutItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
JOptionPane.showMessageDialog(frame, "Multilevel feedback queue simulation application (1.0 version)\n\nCopyright © 2018, 余梓权, All Rights Reserved.");
}
});
}
//创建新进程
public static void createProcess()
{
if(currentPCBsNum == PCBS_MAX_NUM)
{
JOptionPane.showMessageDialog(frame,"The current memory space is full and cannot create a new process!");
}
else
{
currentPCBsNum++;
int randomPid = 1 + (int)(Math.random() * ((100 - 1) + 1));
while(pidsUsed[randomPid] == 1)
{
randomPid = 1 + (int)(Math.random() * ((100 - 1) + 1));
}
pidsUsed[randomPid] = 1;
int randomPriority = 0 + (int)(Math.random() * ((49 - 0) + 1));
int randomLife = 1 + (int)(Math.random() * ((5 - 1) + 1));
PCB pcb = new PCB(randomPid, "Ready", randomPriority, randomLife);
LinkedList<PCB> queue = PCBsQueues[randomPriority].getQueue();
queue.offer(pcb);
PCBsQueues[randomPriority].setQueue(queue);
showPCBQueues(PCBsQueues);
}
}
//开始调度
public static void startMFQSimulation()
{
isStopScheduling = false;
//更新界面操作必须借助多线程来实现
new Thread(new Runnable()
{
@Override
public void run()
{
//当前内存中还留有进程未执行
while(currentPCBsNum!=0 && !isStopScheduling)
{
for(int i = PCBsQueues.length - 1; i >= 0; i--)
{
LinkedList<PCB> queue = PCBsQueues[i].getQueue();
if (queue.size() > 0)
{
//读取该队列首个PCB
PCB pcb = queue.element();
pcb.setStatus("Running");
showPCBQueues(PCBsQueues);
int pid = pcb.getPid();
int priority = pcb.getPriority();
int life = pcb.getLife();
priority = priority / 2;
life = life - 1;
//通过延时一个时间片来模拟该进程的执行
try
{
Thread.sleep((int)(PCBsQueuesTimeSlice[priority] * 1000));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
//若该进程执行完成
if(life == 0)
{
//移除该队列的首个PCB
queue.poll();
pidsUsed[pid] = 0;
currentPCBsNum--;
}
//若该进程还未执行完成,则改变其PCB的相关参数,并插入其优先级所对应的队列尾部
else
{
//移除该队列的首个PCB
queue.poll();
pcb.setPriority(priority);
pcb.setLife(life);
pcb.setStatus("Ready");
LinkedList<PCB> nextQueue = PCBsQueues[priority].getQueue();
nextQueue.offer(pcb);
PCBsQueues[priority].setQueue(nextQueue);
}
break;
}
}
}
initMemory();
showPCBQueues(PCBsQueues);
//所有进程均执行完成,进程调度完成
JOptionPane.showMessageDialog(frame, "Process scheduling over!");
}
}).start();
}
//强制结束进程调度
public static void stopMFQSimulation()
{
isStopScheduling = true;
initMemory();
}
//设置时间片大小
public static void setTimeSlice()
{
String inputMsg = JOptionPane.showInputDialog(frame, "Please input your time slice(seconds):", 0.5);
double timeSliceInput = Double.parseDouble(inputMsg);
while(timeSliceInput <= 0)
{
JOptionPane.showMessageDialog(frame, "Time Slice is illegal, Please set time slice again!");
inputMsg = JOptionPane.showInputDialog(frame, "Please input your time slice(seconds):", "Set Time Slice", JOptionPane.PLAIN_MESSAGE);
timeSliceInput = Integer.parseInt(inputMsg);
}
timeSlice = timeSliceInput;
}
//显示内存中的多级反馈队列
public static void showPCBQueues(PCBsQueue[] PCBsQueues)
{
int queueLocationY = 0;
JPanel queuesPanel = new JPanel();
for(int i = PCBsQueues.length - 1; i >= 0; i--)
{
LinkedList<PCB> queue = PCBsQueues[i].getQueue();
if (queue.size() > 0)
{
//创建一个PCB队列
JPanel PCBsQueue = new JPanel();
// PCBsQueue.setBorder(BorderFactory.createLineBorder(Color.BLACK));
PCBsQueue.setLayout(new FlowLayout(FlowLayout.LEFT));
PCBsQueue.setBounds(0, queueLocationY, 800, 700);
queueLocationY += 50;
//创建队列前面的优先级提示块
JLabel PCBsQueuePriorityLabel = new JLabel("Priority of queue: " + String.valueOf(i));
PCBsQueuePriorityLabel.setOpaque(true);
PCBsQueuePriorityLabel.setBackground(Color.RED);
PCBsQueuePriorityLabel.setForeground(Color.YELLOW);
JPanel PCBsQueuePriorityBlock = new JPanel();
PCBsQueuePriorityBlock.add(PCBsQueuePriorityLabel);
PCBsQueue.add(PCBsQueuePriorityBlock);
for (PCB pcb : queue)
{
//JLabel默认情况下是透明的所以直接设置背景颜色是无法显示的,必须将其设置为不透明才能显示背景
//设置pid标签
JLabel pidLabel = new JLabel("Pid: " + String.valueOf(pcb.getPid()));
pidLabel.setOpaque(true);
pidLabel.setBackground(Color.GREEN);
pidLabel.setForeground(Color.RED);
pidLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
//设置status标签
JLabel statusLabel = new JLabel("Status: " + pcb.getStatus());
statusLabel.setOpaque(true);
statusLabel.setBackground(Color.GREEN);
statusLabel.setForeground(Color.RED);
statusLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
//设置priority标签
JLabel priorityLabel = new JLabel("Priority: " + String.valueOf(pcb.getPriority()));
priorityLabel.setOpaque(true);
priorityLabel.setBackground(Color.GREEN);
priorityLabel.setForeground(Color.RED);
priorityLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
//设置life标签
JLabel lifeLabel = new JLabel("Life: " + String.valueOf(pcb.getLife()));
lifeLabel.setOpaque(true);
lifeLabel.setBackground(Color.GREEN);
lifeLabel.setForeground(Color.RED);
lifeLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
//绘制一个PCB
JPanel PCBPanel = new JPanel();
PCBPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
PCBPanel.setBackground(Color.BLUE);
PCBPanel.add(pidLabel);
PCBPanel.add(statusLabel);
PCBPanel.add(priorityLabel);
PCBPanel.add(lifeLabel);
//将PCB加入队列
PCBsQueue.add(new DrawLinePanel());
PCBsQueue.add(PCBPanel);
}
queuesPanel.add(PCBsQueue);
}
}
//设置queuesPanel中的所有PCB队列(PCBsQueue组件)按垂直方向排列
BoxLayout boxLayout = new BoxLayout(queuesPanel, BoxLayout.Y_AXIS);
queuesPanel.setLayout(boxLayout);
queuesPanel.setSize(800, 700);
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
panel.removeAll();
panel.add(queuesPanel);
panel.updateUI();
panel.repaint();
}
}
//绘制直线类
class DrawLinePanel extends JPanel
{
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawLine(0, this.getSize().height / 2, this.getSize().width, this.getSize().height/2);
}
}
五、相关说明
鉴于上述代码都添加了必要的注释,故这里也不细究代码的细节问题。
- 此次进程调度的模拟实现的关键在于MFQSimulation这个类,GUI的实现关键在于该类中的showPCBQueues静态方法。
- 实现实时更新界面不能使用主线程(默认当前线程即为主线程)来更新,而应借助多线程的技术来实现更新操作。
- 鉴于Swing默认的界面显示风格比较丑,所以使用了Windows风格来显示。
- 设置组件垂直排列,可借助BoxLayout。
- 关于向Panel中动态添加组件并显示的问题:
eg:
JPanel panel = new JPanel(); ...... 监听器方法 { panel.removeAll(); JButton button = new JButton("Hello"); panel.add(button); panel.updateUI(); panel.repaint(); }
当触发该控件,以上代码成功添加并在JPanel中显示了一个按钮。
但是,如果向JPanel中添加“没有布局”的JPanel(这里假设为panel已经被你设置panel.setLayout(null),因为你想要使用绝对布局方式),则最终只是显示一个小黑点,没有成功显示你所添加的panel。
综上,如果要为JPanel添加JPanel,被添加的JPanel一定要带有相应的布局管理器!