[英]Java unable to break out of while loop
我正在嘗試用 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()
使我無法跳出循環?
任何解釋將不勝感激=)
您的問題是因為您使用的是非線程安全boolean
變量timerStarted
此外, while(true)
循環是從主線程運行的,但是您對timerStarted
所做的修改是從另一個線程完成的,這導致意外行為完全取決於運行程序的機器。
這種情況會在更改timerStarted
值和檢查該更改之間產生競爭條件
我是如何檢測到這一點的:
只是我在檢查值更改和修改值時打印了線程 ID
解決方案
檢查這些代碼更改
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
。
您需要閱讀的內容:
AtmoicBoolean
vs boolean(檢查這個: 我什么時候需要在 Java 中使用 AtomicBoolean? )問題是您的代碼不是線程安全的。 具體來說,您沒有做確保timerStarted
讀取看到另一個線程的最新寫入所需的事情。
有多種可能的解決方案,包括:
timerStarted
聲明為volatile
,或synchronized
塊中讀寫timerStarted
,或AtomicBoolean
。這些確保在一個線程寫入標志和第二個線程隨后讀取之間存在發生在之前的關系。 這反過來又保證讀取看到寫入的值。
“為什么沒有 System.out.println() 使我無法跳出循環?”
因為沒有發生之前的關系; 看上面。
一個更有趣的問題是:
“為什么添加 println 時它會起作用?”
System.out.println()
在System.out.println()
做了一些同步。 (流對象是線程安全的,並進行同步以確保其數據結構不會被弄亂。)這顯然足以導致對timerStarted
寫入刷新到主內存。
但是,這並不能保證。 javadocs 沒有指定println
調用的同步行為。 此外,尚不清楚這是否足以在timerStarted
的寫入和讀取之間發生(偶然!)。
參考:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.