我正在尝试在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
是正确的,所以我期望这一部分:
while (TimerTest.timerStarted == false) {
if (TimerTest.timerStarted == true) {break;}
}
打破循环。
相反,当我运行程序并按任意键时,命令行仅显示Keypress indicated.
,并且不会中断该循环。
现在这是奇怪的部分:
我试图像这样向while块中添加一些代码。
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()
使我无法摆脱困境?
任何解释将不胜感激=)
答案 0 :(得分:1)
您的问题是因为您使用的是非线程安全变量boolean
此外,timerStarted
循环是从主线程运行的,但是您在while(true)
上所做的修改是从另一个线程进行的,这使得意外行为完全取决于运行程序的机器。
这种情况使timerStarted
的值更改与检查该更改之间存在竞争条件
我是如何检测到的:
我只是在检查值更改和修改值时打印了线程ID
解决方案
检查这些代码更改
timerStarted
和
static AtomicBoolean timerStarted = new AtomicBoolean(false);
和
while (Scratch.timerStarted.get() == false) {
System.out.println("Thread.ID: " + Thread.currentThread().getId() + " ----> Scratch.timerStarted: " + Scratch.timerStarted);
if (Scratch.timerStarted.get() == true) {
break;
}
}
这里我使用 public void keyPressed(KeyEvent e) {
System.out.println("Keypress indicated.");
Scratch.timerStarted.set(true);
System.out.println("Thread.ID: " + Thread.currentThread().getId() + " ----> Scratch.timerStarted: " + Scratch.timerStarted);
}
代替了线程安全的AtomicBoolean
。
您需要阅读的内容:
boolean
与boolean(请检查:When do I need to use AtomicBoolean in Java?)答案 1 :(得分:1)
问题是您的代码不是线程安全的。具体来说,您没有做确保读取timerStarted
看到其他线程的最新写入所需要的事情。
有许多可能的解决方案,包括:
timerStarted
声明为volatile
,或timerStarted
块中读写synchronized
,或者AtomicBoolean
。这些确保在一个线程写入标志与随后由第二个线程读取标志之间存在一个
“为什么没有System.out.println()使我无法摆脱循环?”
因为没有先发生关系;见上文。
一个更有趣的问题是:
“为什么在添加println时起作用?”
System.out.println()
在后台进行一些同步。 (stream对象是线程安全的,并且进行同步以确保其数据结构不会被弄乱。)显然, 足以使对timerStarted
的写操作刷新到main记忆。
但是,不能保证。 javadocs没有为println
调用指定同步行为。此外,尚不清楚是否足以在timerStarted
的写入和读取之间产生(偶然的!) 。
参考文献: