The*_*viv 9 java concurrency user-interface swing
下面是一个简单的Java Swing程序,它由两个文件组成:
图形用户界面显示"新游戏"按钮,然后显示编号为1到3的其他三个按钮.
如果用户点击其中一个编号按钮,游戏会将相应的数字打印到控制台上.但是,如果用户单击"新游戏"按钮,程序将冻结.
(1)为什么程序会冻结?
(2)如何重写程序来解决问题?
(3)如何更好地编写程序?
Game.java:
public class Game {
private GraphicalUserInterface userInterface;
public Game() {
userInterface = new GraphicalUserInterface(this);
}
public void play() {
int selection = 0;
while (selection == 0) {
selection = userInterface.getSelection();
}
System.out.println(selection);
}
public static void main(String[] args) {
Game game = new Game();
game.play();
}
}
Run Code Online (Sandbox Code Playgroud)
GraphicalUserInterface.java:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GraphicalUserInterface extends JFrame implements ActionListener {
private Game game;
private JButton newGameButton = new JButton("New Game");
private JButton[] numberedButtons = new JButton[3];
private JPanel southPanel = new JPanel();
private int selection;
private boolean isItUsersTurn = false;
private boolean didUserMakeSelection = false;
public GraphicalUserInterface(Game game) {
this.game = game;
newGameButton.addActionListener(this);
for (int i = 0; i < 3; i++) {
numberedButtons[i] = new JButton((new Integer(i+1)).toString());
numberedButtons[i].addActionListener(this);
southPanel.add(numberedButtons[i]);
}
getContentPane().add(newGameButton, BorderLayout.NORTH);
getContentPane().add(southPanel, BorderLayout.SOUTH);
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
public void actionPerformed(ActionEvent event) {
JButton pressedButton = (JButton) event.getSource();
if (pressedButton.getText() == "New Game") {
game.play();
}
else if (isItUsersTurn) {
selection = southPanel.getComponentZOrder(pressedButton) + 1;
didUserMakeSelection = true;
}
}
public int getSelection() {
if (!isItUsersTurn) {
isItUsersTurn = true;
}
if (didUserMakeSelection) {
isItUsersTurn = false;
didUserMakeSelection = false;
return selection;
}
else {
return 0;
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用while循环导致问题
while (selection == 0) {
selection = userInterface.getSelection();
}
Run Code Online (Sandbox Code Playgroud)
在Game.java的play()方法中.
如果第12和第14行被注释掉,
//while (selection == 0) {
selection = userInterface.getSelection();
//}
Run Code Online (Sandbox Code Playgroud)
该程序不再冻结.
我认为这个问题与并发性有关.但是,我想准确理解while循环导致程序冻结的原因.
The*_*viv 17
谢谢各位程序员.我发现答案非常有用.
(1)为什么程序会冻结?
程序首次启动时,game.play()由主线程执行,主线程是执行的线程main.但是,当按下"新游戏"按钮时,game.play()由事件调度线程(而不是主线程)执行,该线程负责执行事件处理代码并更新用户界面.该while环(在play())只有终止,如果selection == 0计算结果为false.只有这样,selection == 0计算结果为false是,如果didUserMakeSelection变true.唯一的办法didUserMakeSelection变得true是,如果用户按下数字按钮之一.但是,用户不能按任何编号按钮,也不能按"新游戏"按钮,也不能退出程序."新游戏"按钮甚至没有弹出,因为事件调度线程(否则会重新绘制屏幕)太忙于执行while循环(由于上述原因,这实际上是有效的).
(2)如何重写程序来解决问题?
由于问题是由game.play()事件调度线程中的执行引起的,因此直接答案是game.play()在另一个线程中执行.这可以通过更换来完成
if (pressedButton.getText() == "New Game") {
game.play();
}
Run Code Online (Sandbox Code Playgroud)
同
if (pressedButton.getText() == "New Game") {
Thread thread = new Thread() {
public void run() {
game.play();
}
};
thread.start();
}
Run Code Online (Sandbox Code Playgroud)
但是,这会产生一个新的(虽然更容易忍受)问题:每次按下"新游戏"按钮时,都会创建一个新线程.由于程序非常简单,因此不是什么大问题; 一旦用户按下编号按钮,这样的线程就变为不活动(即游戏结束).但是,假设完成游戏需要更长的时间.假设,当游戏正在进行时,用户决定开始新的游戏.每次用户开始新游戏时(在完成一个游戏之前),活动线程的数量会增加.这是不合需要的,因为每个活动线程都消耗资源.
新问题可以通过以下方式解决:
(1)将导入语句Executors,ExecutorService以及Future,在Game.java
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
Run Code Online (Sandbox Code Playgroud)
(2)添加单线程执行器作为字段Game
private ExecutorService gameExecutor = Executors.newSingleThreadExecutor();
Run Code Online (Sandbox Code Playgroud)
(3)将Future表示提交给单线程执行程序的最后一个任务的a添加为一个字段Game
private Future<?> gameTask;
Run Code Online (Sandbox Code Playgroud)
(4)添加方法 Game
public void startNewGame() {
if (gameTask != null) gameTask.cancel(true);
gameTask = gameExecutor.submit(new Runnable() {
public void run() {
play();
}
});
}
Run Code Online (Sandbox Code Playgroud)
(5)更换
if (pressedButton.getText() == "New Game") {
Thread thread = new Thread() {
public void run() {
game.play();
}
};
thread.start();
}
Run Code Online (Sandbox Code Playgroud)
同
if (pressedButton.getText() == "New Game") {
game.startNewGame();
}
Run Code Online (Sandbox Code Playgroud)
最后,
(6)更换
public void play() {
int selection = 0;
while (selection == 0) {
selection = userInterface.getSelection();
}
System.out.println(selection);
}
Run Code Online (Sandbox Code Playgroud)
同
public void play() {
int selection = 0;
while (selection == 0) {
selection = userInterface.getSelection();
if (Thread.currentThread().isInterrupted()) {
return;
}
}
System.out.println(selection);
}
Run Code Online (Sandbox Code Playgroud)
要确定if (Thread.currentThread().isInterrupted())检查的位置,请查看方法滞后的位置.在这种情况下,用户必须进行选择.
还有另一个问题.主线程仍然可以处于活动状态.要解决此问题,您可以替换
public static void main(String[] args) {
Game game = new Game();
game.play();
}
Run Code Online (Sandbox Code Playgroud)
同
public static void main(String[] args) {
Game game = new Game();
game.startNewGame();
}
Run Code Online (Sandbox Code Playgroud)
以下代码适用上述修改(除checkThreads()方法外):
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game {
private GraphicalUserInterface userInterface;
private ExecutorService gameExecutor = Executors.newSingleThreadExecutor();
private Future<?> gameTask;
public Game() {
userInterface = new GraphicalUserInterface(this);
}
public static void main(String[] args) {
checkThreads();
Game game = new Game();
checkThreads();
game.startNewGame();
checkThreads();
}
public static void checkThreads() {
ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
ThreadGroup systemThreadGroup = mainThreadGroup.getParent();
System.out.println("\n" + Thread.currentThread());
systemThreadGroup.list();
}
public void play() {
int selection = 0;
while (selection == 0) {
selection = userInterface.getSelection();
if (Thread.currentThread().isInterrupted()) {
return;
}
}
System.out.println(selection);
}
public void startNewGame() {
if (gameTask != null) gameTask.cancel(true);
gameTask = gameExecutor.submit(new Runnable() {
public void run() {
play();
}
});
}
}
class GraphicalUserInterface extends JFrame implements ActionListener {
private Game game;
private JButton newGameButton = new JButton("New Game");
private JButton[] numberedButtons = new JButton[3];
private JPanel southPanel = new JPanel();
private int selection;
private boolean isItUsersTurn = false;
private boolean didUserMakeSelection = false;
public GraphicalUserInterface(Game game) {
this.game = game;
newGameButton.addActionListener(this);
for (int i = 0; i < 3; i++) {
numberedButtons[i] = new JButton((new Integer(i+1)).toString());
numberedButtons[i].addActionListener(this);
southPanel.add(numberedButtons[i]);
}
getContentPane().add(newGameButton, BorderLayout.NORTH);
getContentPane().add(southPanel, BorderLayout.SOUTH);
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
public void actionPerformed(ActionEvent event) {
JButton pressedButton = (JButton) event.getSource();
if (pressedButton.getText() == "New Game") {
game.startNewGame();
Game.checkThreads();
}
else if (isItUsersTurn) {
selection = southPanel.getComponentZOrder(pressedButton) + 1;
didUserMakeSelection = true;
}
}
public int getSelection() {
if (!isItUsersTurn) {
isItUsersTurn = true;
}
if (didUserMakeSelection) {
isItUsersTurn = false;
didUserMakeSelection = false;
return selection;
}
else {
return 0;
}
}
}
Run Code Online (Sandbox Code Playgroud)
参考
Java教程:课程:并发
Java教程:课程:Swing中
的并发Java虚拟机规范,Java SE 7版
Java虚拟机规范,第二版
Eckel,Bruce.用Java思考,第4版."并发与摆动:长期运行的任务",p.988.
如何在同一个线程上取消正在运行的任务并将其替换为新任务?
事件回调在GUI事件处理线程中执行(Swig是单线程的)。在回调中您无法获取任何其他事件,因此您的 while 循环永远不会终止。这并不是要考虑这样一个事实:在 java 中,从多个线程访问的变量应该是易失性的或原子的,或者使用同步原语进行保护。