Спасибо товарищам-программистам. Я нашел ответы очень полезными.
(1) Почему программа зависает?
При первом запуске программы game.play()
выполняется основным потоком, который является потоком, выполняющим main
. Однако при нажатии кнопки «Новая игра» game.play()
запускается потоком отправки событий (вместо основного потока), который является потоком, ответственным за выполнение кода обработки событий и обновление пользователя. интерфейс. Цикл while
(в play()
) завершается, только если selection == 0
оценивается как false
. Единственный способ, которым selection == 0
оценивается как false
, - это если didUserMakeSelection
становится true
. Единственный способ, которым didUserMakeSelection
становится true
, - это если пользователь нажимает одну из пронумерованных кнопок. Однако пользователь не может нажать ни нумерованную кнопку, ни кнопку «Новая игра», ни выйти из программы. Кнопка «Новая игра» даже не выскакивает обратно, потому что поток отправки событий (который в противном случае перерисовал бы экран) слишком занят выполнением цикла while
(который фактически является inifinte по указанным выше причинам).
(2) Как можно переписать программу, чтобы устранить проблему?
Поскольку проблема вызвана выполнением game.play()
в потоке отправки событий, прямой ответ - выполнить game.play()
в другом потоке. Этого можно добиться, заменив
if (pressedButton.getText() == "New Game") {
game.play();
}
с участием
if (pressedButton.getText() == "New Game") {
Thread thread = new Thread() {
public void run() {
game.play();
}
};
thread.start();
}
Однако это приводит к новой (хотя и более терпимой) проблеме: каждый раз, когда нажимается кнопка «Новая игра», создается новый поток. Поскольку программа очень проста, это не имеет большого значения; такой поток становится неактивным (т.е. игра завершается), как только пользователь нажимает пронумерованную кнопку. Однако предположим, что для завершения игры потребовалось больше времени. Предположим, в процессе игры пользователь решает начать новую. Каждый раз, когда пользователь запускает новую игру (до ее завершения), количество активных потоков увеличивается. Это нежелательно, потому что каждый активный поток потребляет ресурсы.
Новую проблему можно исправить:
(1) добавление операторов импорта для Executors
, ExecutorService
и Future
в Game.java
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
(2) добавление однопоточного исполнителя в поле под Game
private ExecutorService gameExecutor = Executors.newSingleThreadExecutor();
(3) добавление Future
, представляющего последнюю задачу, переданную однопотоковому исполнителю, в виде поля под Game
private Future<?> gameTask;
(4) добавление метода под Game
public void startNewGame() {
if (gameTask != null) gameTask.cancel(true);
gameTask = gameExecutor.submit(new Runnable() {
public void run() {
play();
}
});
}
(5) замена
if (pressedButton.getText() == "New Game") {
Thread thread = new Thread() {
public void run() {
game.play();
}
};
thread.start();
}
с участием
if (pressedButton.getText() == "New Game") {
game.startNewGame();
}
и наконец,
(6) замена
public void play() {
int selection = 0;
while (selection == 0) {
selection = userInterface.getSelection();
}
System.out.println(selection);
}
с участием
public void play() {
int selection = 0;
while (selection == 0) {
selection = userInterface.getSelection();
if (Thread.currentThread().isInterrupted()) {
return;
}
}
System.out.println(selection);
}
Чтобы определить, где поставить if (Thread.currentThread().isInterrupted())
проверку, посмотрите, где отстает метод. В этом случае пользователь должен сделать выбор.
Есть еще одна проблема. Основной поток все еще может быть активен. Чтобы исправить это, вы можете заменить
public static void main(String[] args) {
Game game = new Game();
game.play();
}
с участием
public static void main(String[] args) {
Game game = new Game();
game.startNewGame();
}
В приведенном ниже коде применяются указанные выше модификации (в дополнение к методу 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;
}
}
}
использованная литература
Учебники по Java: Урок: параллелизм
Учебники по Java: Урок: Параллелизм в Swing
Спецификация виртуальной машины Java, Java SE 7 Edition
Спецификация виртуальной машины Java, второе издание
Экель, Брюс. Мышление на Java, 4-е издание. «Параллелизм и Swing: длительные задачи», стр. 988.
Как мне отменить запущенную задачу и заменить ее новой в том же потоке?
person
The Aviv
schedule
02.04.2012
while
является ожидание, пока пользователь сделает выбор. МетодgetSelection()
возвращает число, когда настала очередь пользователя. - person The Aviv   schedule 02.04.2012