Спасибо товарищи программисты.Я нашел ответы очень полезными.
(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();
}
на
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 VirtualСпецификация машины, Java SE 7 Edition
Спецификация виртуальной машины Java, второе издание
Eckel, Брюс. Мышление на Java, 4-е издание .«Параллельность и свинг: долгосрочные задачи», с.988.
Как отменить запущенное задание и заменить его новым в том же потоке?