我正在尝试用java构建一个计时器。
预期输出:
当你运行程序时,会出现一个窗口,里面有一个大“0”。
一旦用户按下键盘上的任意键,数字就会每秒增加 1。
这就是我所拥有的。
import javax.swing.*;
import javax.swing.SwingConstants;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
public class TimerTest implements KeyListener {
static final int SCREEN_WIDTH = 960;
static final int SCREEN_HEIGHT = 540;
static boolean timerStarted;
static int keyPressedNum; //amount of times the user pressed any key.
static JLabel label;
static int currentTime;
public static void main(String[] args) {
JFrame frame = new JFrame("TimerTest");
JPanel panel = new JPanel();
label = new JLabel();
label.setFont(new Font("Arial", Font.PLAIN, 256));
label.setText("0");
label.setHorizontalAlignment(SwingConstants.CENTER);
panel.setLayout(new BorderLayout());
panel.add(label, BorderLayout.CENTER);
frame.addKeyListener(new TimerTest());
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
frame.setResizable(false);
frame.setVisible(true);
while (true) {
while (TimerTest.timerStarted == false) {
if (TimerTest.timerStarted == true) {break;}
}
try {Thread.sleep(1000);}
catch (InterruptedException ex) {ex.printStackTrace();}
currentTime++;
label.setText(String.valueOf(currentTime));
}
}
public void keyPressed(KeyEvent e) {
System.out.println("Keypress indicated.");
TimerTest.timerStarted = true;
}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
}
当我按任意键时,程序将 timerStarted
设置为 true。
因为 timerStarted
为 true,所以我期待这部分:
while (TimerTest.timerStarted == false) {
if (TimerTest.timerStarted == true) {break;}
}
跳出循环。
相反,当我运行程序并按任意键时,命令行只会打印:Keypress indicates.
,并且它不会跳出该循环。
现在这是奇怪的部分:
我尝试向 while block 添加一些代码,如下所示。
while (TimerTest.timerStarted == false) {
System.out.println("currently stuck here...");
//(I simply added a println code)
if (TimerTest.timerStarted == true) {break;}
}
令人惊讶的是,当我这样做时,程序就像我想要的那样工作。它会跳出该循环并且计时器开始运行。
这让我很困惑。添加 System.out.println();
解决了问题...?
为什么没有 System.out.println()
让我无法跳出循环?
任何解释将不胜感激 =)
最佳答案
问题是您的代码不是线程安全的。具体来说,您没有执行确保 timerStarted
的读取看到其他线程的最新写入所需的操作。
有许多可能的解决方案,包括:
- 声明
timerStarted
为volatile
,或者 - 在
同步
block 中读取和写入timerStarted
,或者 - 使用
AtomicBoolean
。
这些确保一个线程写入标志与第二个线程随后读取标志之间存在一种发生之前关系。这反过来保证了读取看到写入的值。
"Why does not having System.out.println() makes me unable to break out of the loop?"
因为不存在发生在之前的关系;见上文。
一个更有趣的问题是:
"Why does it work when you add the println?"
System.out.println()
在后台执行一些同步操作。 (流对象是线程安全的,并进行同步以确保其数据结构不会困惑。)这显然足以导致对 timerStarted
的写入刷新到主内存。
但是,这并不能保证。 javadoc 没有指定 println 调用的同步行为。此外,尚不清楚这是否足以在 timerStarted
的写入和读取之间发生(偶然!)。
引用文献:
关于Java无法跳出while循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59509342/