为什么这个简单的 Java Swing 程序会冻结? [英] Why does this simple Java Swing program freeze?

查看:21
本文介绍了为什么这个简单的 Java Swing 程序会冻结?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

下面是一个由两个文件组成的简单 Java Swing 程序:

Below is a simple Java Swing program that consists of two files:

  • Game.java
  • GraphicalUserInterface.java

图形用户界面显示一个新游戏"按钮,然后是其他三个编号为 1 到 3 的按钮.

The graphical user interface displays a "New Game" button, followed by three other buttons numbered 1 to 3.

如果用户点击其中一个编号按钮,游戏会在控制台上打印出相应的数字.但是,如果用户点击新游戏"按钮,程序就会冻结.

If the user clicks on one of the numbered buttons, the game prints out the corresponding number onto the console. However, if the user clicks on the "New Game" button, the program freezes.

(1) 为什么程序会死机?

(1) Why does the program freeze?

(2) 如何重写程序来解决问题?

(2) How can the program be rewritten to fix the problem?

(3) 程序一般如何写得更好?

(3) How can the program be better written in general?

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();
    }

}

<小时>

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;
        }
    }

}

<小时>

问题源于使用 while 循环

while (selection == 0) {
    selection = userInterface.getSelection();
}

Game.javaplay()方法中.

如果第 12 和 14 行被注释掉,

If lines 12 and 14 are commented out,

//while (selection == 0) {
    selection = userInterface.getSelection();
//}

程序不再冻结.

我认为问题与并发有关.但是,我想准确了解为什么 while 循环会导致程序冻结.

I think the problem is related to concurrency. However, I would like to gain a precise understanding of why the while loop causes the program to freeze.

推荐答案

谢谢各位程序员.我发现这些答案非常有用.

Thank you fellow programmers. I found the answers very useful.

(1) 为什么程序会死机?

(1) Why does the program freeze?

当程序第一次启动时,game.play()主线程执行,也就是执行main的线程.然而,当按下新游戏"按钮时,game.play()事件调度线程(而不是主线程)执行,这是线程负责执行事件处理代码和更新用户界面.while 循环(在 play() 中)仅在 selection == 0 计算结果为 false 时才终止.selection == 0 评估为 false 的唯一方法是 didUserMakeSelection 变为 true.didUserMakeSelection 变为 true 的唯一方法是用户按下编号按钮之一.但是,用户不能按任何编号按钮,也不能按新游戏"按钮,也不能退出程序.新游戏"按钮甚至不会弹出,因为事件调度线程(否则会重新绘制屏幕)太忙于执行 while 循环(由于上述原因,它实际上是 inifinte).

When the program first starts, game.play() gets executed by the main thread, which is the thread that executes main. However, when the "New Game" button is pressed, game.play() gets executed by the event dispatch thread (instead of the main thread), which is the thread responsible for executing event-handling code and updating the user interface. The while loop (in play()) only terminates if selection == 0 evaluates to false. The only way selection == 0 evaluates to false is if didUserMakeSelection becomes true. The only way didUserMakeSelection becomes true is if the user presses one of the numbered buttons. However, the user cannot press any numbered button, nor the "New Game" button, nor exit the program. The "New Game" button doesn't even pop back out, because the event dispatch thread (which would otherwise repaint the screen) is too busy executing the while loop (which is effectively inifinte for the above reasons).

(2) 如何重写程序来解决问题?

(2) How can the program be rewritten to fix the problem?

由于问题是在事件调度线程中执行game.play()引起的,直接的答案是在另一个线程中执行game.play()线.这可以通过替换

Since the problem is caused by the execution of game.play() in the event dispatch thread, the direct answer is to execute game.play() in another thread. This can be accomplished by replacing

if (pressedButton.getText() == "New Game") {
    game.play();
}

if (pressedButton.getText() == "New Game") {
    Thread thread = new Thread() {
        public void run() {
            game.play();
        }
    };
    thread.start();
}

然而,这会导致一个新的(尽管更容易忍受)问题:每次按下新游戏"按钮时,都会创建一个新线程.由于程序非常简单,所以没什么大不了的;只要用户按下编号按钮,这样的线程就会变为非活动状态(即游戏结束).但是,假设完成游戏需要更长的时间.假设,当游戏正在进行时,用户决定开始新的游戏.每次用户开始新游戏时(在完成一个游戏之前),活动线程的数量都会增加.这是不可取的,因为每个活动线程都会消耗资源.

However, this results in a new (albeit more tolerable) problem: Each time the "New Game" button is pressed, a new thread is created. Since the program is very simple, it's not a big deal; such a thread becomes inactive (i.e. a game finishes) as soon as the user presses a numbered button. However, suppose it took longer to finish a game. Suppose, while a game is in progress, the user decides to start a new one. Each time the user starts a new game (before finishing one), the number of active threads increments. This is undesirable, because each active thread consumes resources.

新问题可以通过以下方式解决:

The new problem can be fixed by:

(1)在Game.java中为ExecutorsExecutorServiceFuture添加import语句/p>

(1) adding import statements for Executors, ExecutorService, and Future, in 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下的一个字段>

(3) adding a Future, representing the last task submitted to the single-thread executor, as a field under 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()) 检查放在哪里,请查看方法滞后的位置.在这种情况下,这是用户必须进行选择的地方.

To determine where to put the if (Thread.currentThread().isInterrupted()) check, look at where the method lags. In this case, it is where the user has to make a selection.

还有一个问题.主线程可能仍处于活动状态.要解决此问题,您可以替换

There is another problem. The main thread could still be active. To fix this, you can replace

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() 方法):

The code below applies the above modifications (in addition to a checkThreads() method):

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("
" + 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 版
Java 虚拟机规范,第二版
埃克尔,布鲁斯.用 Java 思考,第 4 版.并发和摇摆:长时间运行的任务",第 10 页.988.
如何取消正在运行的任务并将其替换为同一线程上的新任务?

The Java Tutorials: Lesson: Concurrency
The Java Tutorials: Lesson: Concurrency in Swing
The Java Virtual Machine Specification, Java SE 7 Edition
The Java Virtual Machine Specification, Second Edition
Eckel, Bruce. Thinking in Java, 4th Edition. "Concurrency & Swing: Long-running tasks", p. 988.
How do I cancel a running task and replace it with a new one, on the same thread?

这篇关于为什么这个简单的 Java Swing 程序会冻结?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆