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

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

问题描述

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




  • Game.java li>
  • GraphicalUserInterface.java



图形用户界面显示New



如果用户点击其中一个编号按钮,游戏就会在控制台上打印出相应的数字。



(1)为什么程序冻结?


如果用户点击New Game按钮,

(2)如何重写程序来修复问题?



(3)如何更好地编写程序? / p>



Game.java p>

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

}





$ b b

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 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(游戏游戏){
this.game = game;

newGameButton.addActionListener(this);

for(int i = 0; i <3; i ++){
numberedButtons [i] = new JButton(new Integer(i + 1))。
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 select;
}
else {
return 0;
}
}

}






问题是使用循环

产生的结果

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



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

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

程序不再冻结。



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

解决方案

感谢程序员。我发现答案非常有用。


(1)为什么程序冻结?


程序第一次启动时, c> game.play()由主线程执行是执行 main 的线程。但是,当按下新游戏按钮时,事件分派线程(而不是主线程)执行 game.play() ),这是负责执行事件处理代码和更新用户界面的线程。 while 循环( play())只会终止 selection == 0 计算为 false 。唯一的方法是 selection == 0 计算 false didUserMakeSelection 变为 true didUserMakeSelection 成为 true 的唯一方法是如果用户按下其中一个编号按钮。但是,用户不能按任何数字按钮,也不能按新游戏按钮,也不退出程序。 新游戏按钮甚至不弹出,因为事件分派线程(否则会重新绘制屏幕)太忙执行循环


(2)如何重写程序以修复此问题?


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

  if(pressedButton.getText()==New Game){
。玩();
}

  if(pressedButton.getText()==New Game){
Thread thread = new Thread(){
public void run(){
game.play();
}
};
thread.start();然而,这导致了一个新的(虽然更容忍)问题:每次都会有一个新的(但是更容忍的)问题:
}



<按下新游戏按钮,创建新的线程。因为程序很简单,这不是什么大事;这样的线程一旦用户按下编号的按钮就变得不活动(即,游戏完成)。但是,假设游戏结束需要更长时间。假设,当游戏正在进行时,用户决定开始一个新的。每次用户开始新游戏(在完成一个游戏之前),活动线程的数量增加。这是不可取的,因为每个活动线程都消耗资源。



新的问题可以通过修复:



1)为 Executors ExecutorService 添加import语句,in Game.java

  import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

(2)添加一个单线程执行器 code>游戏

  private ExecutorService gameExecutor = Executors.newSingleThreadExecutor(); 

(3)添加未来最后一个任务提交到单线程执行程序,作为 Game 下的字段

  private Future<?> gameTask; 


(4)在游戏下添加方法

  public void startNewGame(){
if(gameTask!= null)gameTask.cancel(true);
gameTask = gameExecutor.submit(new Runnable(){
public void run(){
play();
}
});
}

(5)替换

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

  if(pressedButton.getText()==New Game){
game.startNewGame
}

最后,



(6)替换

  public void play(){
int selection =

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

要确定 currentThread()。isInterrupted())检查,看看方法滞后的地方。在这种情况下,它是用户必须进行选择的地方。



还有另一个问题。主线程仍然可以是活动的。要解决此问题,您可以替换

  public static void main(String [] args){
Game game = new游戏();
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(\\\
+ 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(游戏游戏){
this.game = game;

newGameButton.addActionListener(this);

for(int i = 0; i <3; i ++){
numberedButtons [i] = new JButton(new Integer(i + 1))。
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 select;
}
else {
return 0;
}
}
}

参考



Java教程:课程:并发 < br>
Java教程:Lesson:Swing中的并发性

Java虚拟机规范Java SE 7 Edition

Java虚拟机规范第二版

Eckel,Bruce。 Thinking in Java,4th Edition 。 Concurrency& Swing:Long-running tasks,p。 967.


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

  • Game.java
  • GraphicalUserInterface.java

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) Why does the program freeze?

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

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

Source

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

}


The problem results from using the while loop

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

in the play() method of Game.java.

If lines 12 and 14 are commented out,

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

the program no longer freezes.

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) Why does the program freeze?

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) How can the program be rewritten to fix the problem?

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

with

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) 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) adding a single-thread executor as a field under Game

private ExecutorService gameExecutor = Executors.newSingleThreadExecutor();

(3) adding a Future, representing the last task submitted to the single-thread executor, as a field under Game

private Future<?> gameTask;

(4) adding a method under Game

public void startNewGame() {
    if (gameTask != null) gameTask.cancel(true);
    gameTask = gameExecutor.submit(new Runnable() {
        public void run() {
            play();
        }
    });
}

(5) replacing

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

with

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

and finally,

(6) replacing

public void play() {
    int selection = 0;

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

    System.out.println(selection);
}

with

public void play() {
    int selection = 0;

    while (selection == 0) {
        selection = userInterface.getSelection();
        if (Thread.currentThread().isInterrupted()) {
            return;
        }
    }

    System.out.println(selection);
}

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

with

public static void main(String[] args) {
    Game game = new Game();
    game.startNewGame();
}

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("\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;
        }
    }
}

References

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天全站免登陆