在不冻结 Swing GUI 的情况下使用 Java wait() 和 notify()
Using Java wait() and notify() without freezing Swing GUI
我知道 Swing GUI 本身使用线程,但我正在尝试使用单独的线程来 运行 我的模拟。我创建了一个 class 来实现 Runnable 并像大多数简单的 Runnable 示例一样使用自定义线程。我的 运行() 方法基本上是 运行 我的模拟,每秒更新一次(效果很好),但我现在正在尝试实现可以 pause/resume 模拟的按钮。我的开始按钮成功启动线程,我的暂停按钮成功暂停线程。但是,当 selected 暂停时,整个 GUI 暂停,您可以看到按钮仍在 selected 并且我无法 select 任何按钮或与 GUI 交互根本。一旦我在我的自定义线程上调用 wait() ,我的整个 GUI 就会停止,即使我使用的是与该 GUI 分开的线程。 为什么调用 wait() 会冻结我的 GUI?我怎样才能暂停这个特定的线程而不是整个 GUI?
请注意,“开始”按钮应该是让程序继续运行的按钮。这是我的 GUI 代码:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GridFrame extends JFrame {
private static final long serialVersionUID = 2857470112009359285L;
private MyGridPanel grid;
private Simulation sim;
GridFrame(Simulation sim, int w, int h, int rows, int cols) {
this.sim = sim;
setTitle("Simulation");
setSize(w, h);
grid = new MyGridPanel(w, h, rows, cols);
add(grid, BorderLayout.CENTER);
//Build bottom panel
JPanel buttons = new JPanel();
buttons.setLayout(new GridLayout(1,3));
JButton start = new JButton("Start");
JButton pause = new JButton("Pause");
JButton reset = new JButton("Reset");
start.setActionCommand("Start");
start.addActionListener(new ButtonActionListener());
pause.setActionCommand("Pause");
pause.addActionListener(new ButtonActionListener());
reset.setActionCommand("Reset");
reset.addActionListener(new ButtonActionListener());
buttons.add(start);
buttons.add(pause);
buttons.add(reset);
add(buttons, BorderLayout.SOUTH);
}
public MyGridPanel getGrid(){
return grid;
}
private class ButtonActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
switch(e.getActionCommand()){
case "Start":
System.out.println("Start");
sim.start();
break;
case "Pause":
System.out.println("Pause");
sim.pause();
break;
case "Reset":
System.out.println("Reset");
break;
}
}
}
}
这是我的 Runnable:
public class Simulation implements Runnable{
private Thread t;
private GridFrame frame;
private boolean paused;
public Simulation(){
frame = new GridFrame(this, 300, 300, 10, 10);
frame.setVisible(true);
paused = true;
}
public void start () {
if(t == null){
//Thread has not been created. Simulation has not started to run yet
System.out.println("Starting thread.");
t = new Thread(this);
t.start();
paused = false;
}
else if(paused){
//Simulation and thread already started to run but was paused. This should use notify() to resume the thread.
resume();
paused = false;
}
}
public void resume(){
synchronized(t){
t.notify();
}
}
public void pause(){
synchronized(t){
try {
t.wait();
paused = true;
} catch (InterruptedException e) {
System.out.println("Exception when trying to pause simulation");
e.printStackTrace();
}
}
}
@Override
public void run() {
while(true){
frame.getGrid().step();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread interrupted while simulation was running.");
e.printStackTrace();
}
}
}
public static void main(String[] a) {
Simulation s = new Simulation();
}
}
在 Thread
对象上调用 wait
和 notify
的行为与在任何其他对象上的行为没有区别。具体来说,正如您所注意到的,它不会向正在执行的线程发送应该暂停的信号,而是会阻塞调用线程(在本例中为您的 UI 线程),直到它收到 notify
消息.
您需要实现从 UI 线程到后台线程的消息传递系统(例如阻塞队列),以便获得您想要的内容。
我知道 Swing GUI 本身使用线程,但我正在尝试使用单独的线程来 运行 我的模拟。我创建了一个 class 来实现 Runnable 并像大多数简单的 Runnable 示例一样使用自定义线程。我的 运行() 方法基本上是 运行 我的模拟,每秒更新一次(效果很好),但我现在正在尝试实现可以 pause/resume 模拟的按钮。我的开始按钮成功启动线程,我的暂停按钮成功暂停线程。但是,当 selected 暂停时,整个 GUI 暂停,您可以看到按钮仍在 selected 并且我无法 select 任何按钮或与 GUI 交互根本。一旦我在我的自定义线程上调用 wait() ,我的整个 GUI 就会停止,即使我使用的是与该 GUI 分开的线程。 为什么调用 wait() 会冻结我的 GUI?我怎样才能暂停这个特定的线程而不是整个 GUI?
请注意,“开始”按钮应该是让程序继续运行的按钮。这是我的 GUI 代码:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GridFrame extends JFrame {
private static final long serialVersionUID = 2857470112009359285L;
private MyGridPanel grid;
private Simulation sim;
GridFrame(Simulation sim, int w, int h, int rows, int cols) {
this.sim = sim;
setTitle("Simulation");
setSize(w, h);
grid = new MyGridPanel(w, h, rows, cols);
add(grid, BorderLayout.CENTER);
//Build bottom panel
JPanel buttons = new JPanel();
buttons.setLayout(new GridLayout(1,3));
JButton start = new JButton("Start");
JButton pause = new JButton("Pause");
JButton reset = new JButton("Reset");
start.setActionCommand("Start");
start.addActionListener(new ButtonActionListener());
pause.setActionCommand("Pause");
pause.addActionListener(new ButtonActionListener());
reset.setActionCommand("Reset");
reset.addActionListener(new ButtonActionListener());
buttons.add(start);
buttons.add(pause);
buttons.add(reset);
add(buttons, BorderLayout.SOUTH);
}
public MyGridPanel getGrid(){
return grid;
}
private class ButtonActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
switch(e.getActionCommand()){
case "Start":
System.out.println("Start");
sim.start();
break;
case "Pause":
System.out.println("Pause");
sim.pause();
break;
case "Reset":
System.out.println("Reset");
break;
}
}
}
}
这是我的 Runnable:
public class Simulation implements Runnable{
private Thread t;
private GridFrame frame;
private boolean paused;
public Simulation(){
frame = new GridFrame(this, 300, 300, 10, 10);
frame.setVisible(true);
paused = true;
}
public void start () {
if(t == null){
//Thread has not been created. Simulation has not started to run yet
System.out.println("Starting thread.");
t = new Thread(this);
t.start();
paused = false;
}
else if(paused){
//Simulation and thread already started to run but was paused. This should use notify() to resume the thread.
resume();
paused = false;
}
}
public void resume(){
synchronized(t){
t.notify();
}
}
public void pause(){
synchronized(t){
try {
t.wait();
paused = true;
} catch (InterruptedException e) {
System.out.println("Exception when trying to pause simulation");
e.printStackTrace();
}
}
}
@Override
public void run() {
while(true){
frame.getGrid().step();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread interrupted while simulation was running.");
e.printStackTrace();
}
}
}
public static void main(String[] a) {
Simulation s = new Simulation();
}
}
在 Thread
对象上调用 wait
和 notify
的行为与在任何其他对象上的行为没有区别。具体来说,正如您所注意到的,它不会向正在执行的线程发送应该暂停的信号,而是会阻塞调用线程(在本例中为您的 UI 线程),直到它收到 notify
消息.
您需要实现从 UI 线程到后台线程的消息传递系统(例如阻塞队列),以便获得您想要的内容。