If..Else 语句在 JFrame 程序中行为不可预测(不确定?)
If..Else statement behaving unpredictably (indeterministically?) in a JFrame program
编辑:一位用户标记我的问题可能与这个问题重复:“What is the volatile keyword useful for”,其标题是 "What is the volatile keyword useful for?"。我阅读了问题,但看不出它与我的问题有什么关系。
这是一个用两个 .java 文件编写的程序。
我的问题涉及 main 方法中的 if..else..
注意在下面的代码中,else {..} 中的单行被注释掉了。我将调用此程序的 "version 1",我将调用带有在 "version 2".
中注释的那一行的程序
// -------------
// The code below is in IfElseBugProgram.java
public class IfElseBugProgram {
public static void main(String[] args) {
MyJFrame terminal = new MyJFrame();
while (true) {
String keyReleased = terminal.getKeyReleased();
if (! keyReleased.equals("") )
{
System.out.print("@" + keyReleased);
}
else
{
// System.out.print("!" + keyReleased);
}
}
}
}
// -----
//The code below is in file MyJFrame.java
import javax.swing.JFrame;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
public class MyJFrame extends JFrame implements KeyListener
{
private List<KeyEvent> keyEventQueue;
public MyJFrame()
{
keyEventQueue = new ArrayList<KeyEvent>();
this.addKeyListener(this);
pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
public void keyPressed(KeyEvent e)
{
}
public void keyTyped(KeyEvent e)
{
}
public void keyReleased(KeyEvent keyEvent)
{
keyEventQueue.add(keyEvent);
System.out.println("some key was released!" + keyEventQueue.size());
}
public String getKeyReleased()
{
int i = keyEventQueue.size();
if (i == 0)
{
return ("");
}
else
{
return ("" + i);
}
}
}
我希望 if {...} 中的代码是 运行,一旦我按下键盘上的某个键。也就是说,我期望
System.out.print("@" + keyReleased);
一旦我按下一个键,代码就是 运行。
对于版本 1,我 从来没有 似乎 System.out.print("@" + keyReleased);
变成了 运行;控制台中永远不会打印“@1”或“@2”或“@3”等。
对于版本 2(即,else {..} 块中的代码重新注释),
- 通常发生的是打印出“!”的打印语句。重复获取 运行,直到我按下一个键。那时,“@1”或“@2”等内容会重复打印。
- 有时会发生的是我没有得到“!”也没有打印出“@1”或“@2”! (使用相同的源代码!)
问题:为什么 if {..} 中的 System.out.print("@" + keyReleased);
行在版本 1 中不阻塞 运行,但(通常)在版本 2 中阻塞?
补充观察:
无论我如何更改代码,MyJFrame#keyReleased() 中的打印语句 System.out.println("some key was released!" + keyEventQueue.size());
总是打印出来。
我认为我的 Netbeans 控制台可能有问题。但是我尝试了 运行ing if {..} 块中的其他代码,例如代码行 java.awt.Toolkit.getDefaultToolkit().beep();
,它发出了声音。我还尝试在 JFrame 本身中显示一些图形。结果是一样的:把 else {...} 中的那行代码注释掉,if {..} 块中的代码似乎没有 运行;但是如果我把 else {..} 中的代码行重新注释掉,if {...} 块中的代码(包括发出声音或在 JFrame 中显示某些内容)实际上会 运行。
我很确定 if if (! keyReleased.equals("") )
的条件是正确的。我尝试在 if (! keyReleased.equals("") )
上方添加一行代码 System.out.println("?" + (! keyReleased.equals("")) );
,并且始终在我的控制台上重复打印“?false”,直到我按下一个键,此时我得到“? true”重复打印。
而且:奇怪的是,在版本 1 中将这一行代码 (System.out.println("?" + (! keyReleased.equals("")) );)
放在 if
之上会导致以下行if {..} 块中的代码到现在 运行?!
(可选)背景:
这部分解释了我最终要编写的代码。如果您希望提出一种可能使我实现此目标的方法(与我上面使用的方法不同),请随时提出建议。
我正在尝试构建一个名为 MyJFrame 的简单 class,它可以帮助激励朋友学习如何编程。这个想法是让他通过编写极其简单的游戏,以一种视觉激励的方式学习变量,就像我作为 child 学习 BASIC 时学到的那样。我希望他能够编写一个程序,完全包含在一个 main() 方法中;我希望他有一种在屏幕上绘制字符串的简单方法,以及一种获取用户输入的简单方法。示例程序可能如下所示:
int playerLocationX = 3;
int playerLocationY = 4;
MyJFrame terminal = new MyJFrame();
while(true)
{
// erase player that was drawn during the previous iteration of this loop
terminal.write(" ", playerLocationX, playerLocationY);
// get a key the user last pressed (if the user pressed a key) and update
// player location
String keyReleased = terminal.getKeyReleased();
if (keyReleased.equals("LEFT"))
{
playerLocationX = playerLocationX - 1;
}
else if (keyReleased.equals("RIGHT"))
{
playerLocationY = playerLocationY + 1;
}
// draw player again, using most recent player location
terminal.write("@", playerLocationX, playerLocationY);
}
我不想让他需要编写自己的keyReleased() 方法(即实现KeyListener 接口),因为这需要编写自己的方法的知识;它还需要了解 objects,因为 keyReleased() 方法无法修改存储在 main() 中的局部变量 playerLocationX 和 playerLocationY。
您遇到这种不可预测的行为的主要原因是因为您实际上创建了一个 multi-threaded 程序,您在其中从 事件调度线程。
特别是行:
MyJFrame terminal = new MyJFrame();
启动 Swing 事件调度线程但是行(例如):
String keyReleased = terminal.getKeyReleased();
从主线程访问 terminal
(一个 Swing 组件)。
来自Swing package documentation:
In general Swing is not thread safe. All Swing components and related classes, unless otherwise documented, must be accessed on the event dispatching thread.
在继续尝试使此代码工作之前,我建议您阅读教程 Lesson: Concurrency in Swing。
允许 Java 虚拟机通过假设变量不会被并发线程修改来优化连续的、非同步的加载。
如果你想在一个将被另一个线程更改的字段上进行自旋循环,你可以让虚拟机通过将其标记为 volatile
:[=16 来确保每次读取都能看到最新的更改=]
private volatile List<KeyEvent> keyEventQueue;
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
我不知道你的 V2 是否保证按照 JLS 工作,但是 System.out
PrintStream
将在每次写入时同步,限制允许 VM 进行的优化.
编辑:一位用户标记我的问题可能与这个问题重复:“What is the volatile keyword useful for”,其标题是 "What is the volatile keyword useful for?"。我阅读了问题,但看不出它与我的问题有什么关系。
这是一个用两个 .java 文件编写的程序。 我的问题涉及 main 方法中的 if..else..
注意在下面的代码中,else {..} 中的单行被注释掉了。我将调用此程序的 "version 1",我将调用带有在 "version 2".
中注释的那一行的程序// -------------
// The code below is in IfElseBugProgram.java
public class IfElseBugProgram {
public static void main(String[] args) {
MyJFrame terminal = new MyJFrame();
while (true) {
String keyReleased = terminal.getKeyReleased();
if (! keyReleased.equals("") )
{
System.out.print("@" + keyReleased);
}
else
{
// System.out.print("!" + keyReleased);
}
}
}
}
// -----
//The code below is in file MyJFrame.java
import javax.swing.JFrame;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
public class MyJFrame extends JFrame implements KeyListener
{
private List<KeyEvent> keyEventQueue;
public MyJFrame()
{
keyEventQueue = new ArrayList<KeyEvent>();
this.addKeyListener(this);
pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
public void keyPressed(KeyEvent e)
{
}
public void keyTyped(KeyEvent e)
{
}
public void keyReleased(KeyEvent keyEvent)
{
keyEventQueue.add(keyEvent);
System.out.println("some key was released!" + keyEventQueue.size());
}
public String getKeyReleased()
{
int i = keyEventQueue.size();
if (i == 0)
{
return ("");
}
else
{
return ("" + i);
}
}
}
我希望 if {...} 中的代码是 运行,一旦我按下键盘上的某个键。也就是说,我期望
System.out.print("@" + keyReleased);
一旦我按下一个键,代码就是 运行。
对于版本 1,我 从来没有 似乎 System.out.print("@" + keyReleased);
变成了 运行;控制台中永远不会打印“@1”或“@2”或“@3”等。
对于版本 2(即,else {..} 块中的代码重新注释),
- 通常发生的是打印出“!”的打印语句。重复获取 运行,直到我按下一个键。那时,“@1”或“@2”等内容会重复打印。
- 有时会发生的是我没有得到“!”也没有打印出“@1”或“@2”! (使用相同的源代码!)
问题:为什么 if {..} 中的 System.out.print("@" + keyReleased);
行在版本 1 中不阻塞 运行,但(通常)在版本 2 中阻塞?
补充观察:
无论我如何更改代码,MyJFrame#keyReleased() 中的打印语句
System.out.println("some key was released!" + keyEventQueue.size());
总是打印出来。我认为我的 Netbeans 控制台可能有问题。但是我尝试了 运行ing if {..} 块中的其他代码,例如代码行
java.awt.Toolkit.getDefaultToolkit().beep();
,它发出了声音。我还尝试在 JFrame 本身中显示一些图形。结果是一样的:把 else {...} 中的那行代码注释掉,if {..} 块中的代码似乎没有 运行;但是如果我把 else {..} 中的代码行重新注释掉,if {...} 块中的代码(包括发出声音或在 JFrame 中显示某些内容)实际上会 运行。我很确定 if
if (! keyReleased.equals("") )
的条件是正确的。我尝试在if (! keyReleased.equals("") )
上方添加一行代码System.out.println("?" + (! keyReleased.equals("")) );
,并且始终在我的控制台上重复打印“?false”,直到我按下一个键,此时我得到“? true”重复打印。
而且:奇怪的是,在版本 1 中将这一行代码(System.out.println("?" + (! keyReleased.equals("")) );)
放在if
之上会导致以下行if {..} 块中的代码到现在 运行?!
(可选)背景:
这部分解释了我最终要编写的代码。如果您希望提出一种可能使我实现此目标的方法(与我上面使用的方法不同),请随时提出建议。
我正在尝试构建一个名为 MyJFrame 的简单 class,它可以帮助激励朋友学习如何编程。这个想法是让他通过编写极其简单的游戏,以一种视觉激励的方式学习变量,就像我作为 child 学习 BASIC 时学到的那样。我希望他能够编写一个程序,完全包含在一个 main() 方法中;我希望他有一种在屏幕上绘制字符串的简单方法,以及一种获取用户输入的简单方法。示例程序可能如下所示:
int playerLocationX = 3;
int playerLocationY = 4;
MyJFrame terminal = new MyJFrame();
while(true)
{
// erase player that was drawn during the previous iteration of this loop
terminal.write(" ", playerLocationX, playerLocationY);
// get a key the user last pressed (if the user pressed a key) and update
// player location
String keyReleased = terminal.getKeyReleased();
if (keyReleased.equals("LEFT"))
{
playerLocationX = playerLocationX - 1;
}
else if (keyReleased.equals("RIGHT"))
{
playerLocationY = playerLocationY + 1;
}
// draw player again, using most recent player location
terminal.write("@", playerLocationX, playerLocationY);
}
我不想让他需要编写自己的keyReleased() 方法(即实现KeyListener 接口),因为这需要编写自己的方法的知识;它还需要了解 objects,因为 keyReleased() 方法无法修改存储在 main() 中的局部变量 playerLocationX 和 playerLocationY。
您遇到这种不可预测的行为的主要原因是因为您实际上创建了一个 multi-threaded 程序,您在其中从 事件调度线程。
特别是行:
MyJFrame terminal = new MyJFrame();
启动 Swing 事件调度线程但是行(例如):
String keyReleased = terminal.getKeyReleased();
从主线程访问 terminal
(一个 Swing 组件)。
来自Swing package documentation:
In general Swing is not thread safe. All Swing components and related classes, unless otherwise documented, must be accessed on the event dispatching thread.
在继续尝试使此代码工作之前,我建议您阅读教程 Lesson: Concurrency in Swing。
允许 Java 虚拟机通过假设变量不会被并发线程修改来优化连续的、非同步的加载。
如果你想在一个将被另一个线程更改的字段上进行自旋循环,你可以让虚拟机通过将其标记为 volatile
:[=16 来确保每次读取都能看到最新的更改=]
private volatile List<KeyEvent> keyEventQueue;
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
我不知道你的 V2 是否保证按照 JLS 工作,但是 System.out
PrintStream
将在每次写入时同步,限制允许 VM 进行的优化.