简体   繁体   English

Java无法跳出while循环

[英]Java unable to break out of while loop

I am trying to build a timer in java.我正在尝试用 Java 构建一个计时器。

Expected Output:预期输出:

When you run the program, there's a window with a big "0" in it.运行程序时,会出现一个带有大“0”的窗口。

Once the user presses any key from the keyboard, the number increases by 1 every second.一旦用户按下键盘上的任意键,数字就会每秒增加 1。

Here's what I have.这就是我所拥有的。

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

When I press any key, the program sets timerStarted as true.当我按任意键时,程序将timerStarted设置为 true。

Because timerStarted is true, I was expecting this part:因为timerStarted是真的,我期待这部分:

    while (TimerTest.timerStarted == false) {
            if (TimerTest.timerStarted == true) {break;}
    }

to break out of the loop.跳出循环。

Instead when I run the program and press any key, the command line just prints: Keypress indicated.相反,当我运行程序并按任意键时,命令行只打印: Keypress indicated. , and it doesn't break out of that loop. ,并且它不会跳出该循环。

Now here's the weird part:现在这是奇怪的部分:

I tried adding some code to the while block, like this.我尝试在 while 块中添加一些代码,就像这样。

  while (TimerTest.timerStarted == false) {
        System.out.println("currently stuck here...");
        //(I simply added a println code)
        if (TimerTest.timerStarted == true) {break;}
  }

Surprisingly when I do this, the program works just like how I wanted.令人惊讶的是,当我这样做时,程序就像我想要的那样工作。 It breaks out of that loop and the timer runs.它跳出该循环并且计时器运行。

This confuses me.这让我很困惑。 Adding a System.out.println();添加一个System.out.println(); fixes the issue...?解决问题...?

Why does not having System.out.println() makes me unable to break out of the loop?为什么没有System.out.println()使我无法跳出循环?

Any explanations would be appreciated =)任何解释将不胜感激=)

Your issue is because you are using a non thread-safe boolean variable timerStarted您的问题是因为您使用的是非线程安全boolean变量timerStarted

moreover the while(true) loop is running from main thread but the modification you do on timerStarted is done from another thread this gives unexpected behavior completely depends on the machine that runs the program.此外, while(true)循环是从主线程运行的,但是您对timerStarted所做的修改是从另一个线程完成的,这导致意外行为完全取决于运行程序的机器。

This situation is making a race-condition between changing value of timerStarted and checking for that change这种情况会在更改timerStarted值和检查该更改之间产生竞争条件

How did I detected this:我是如何检测到这一点的:

Simply I printed the thread id while checking for value changes and while modifying the value只是我在检查值更改和修改值时打印了线程 ID

The solution解决方案

check these code changes检查这些代码更改

static AtomicBoolean timerStarted = new AtomicBoolean(false);

and

   while (Scratch.timerStarted.get() == false) {
        System.out.println("Thread.ID: " + Thread.currentThread().getId() + " ----> Scratch.timerStarted: " + Scratch.timerStarted);
        if (Scratch.timerStarted.get() == true) {
            break;
        }
    }

and

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

here I used AtomicBoolean instead of boolean which is thread safe.在这里,我使用了AtomicBoolean而不是线程安全的boolean


What you need to read about:您需要阅读的内容:

  1. Multi-threading in java (check this: https://www.javatpoint.com/multithreading-in-java ) java中的多线程(检查这个: https : //www.javatpoint.com/multithreading-in-java
  2. AtmoicBoolean vs boolean (check this: When do I need to use AtomicBoolean in Java? ) AtmoicBoolean vs boolean(检查这个: 我什么时候需要在 Java 中使用 AtomicBoolean?

The problem is that your code is not thread-safe.问题是您的代码不是线程安全的。 Specifically, you are not doing the things that are needed to ensure that reads of timerStarted see the most recent write by the other thread.具体来说,您没有做确保timerStarted读取看到另一个线程的最新写入所需的事情。

There are number of possible solutions, including:有多种可能的解决方案,包括:

  • declare timerStarted as volatile , ortimerStarted声明为volatile ,或
  • read and write timerStarted in a synchronized block, orsynchronized块中读写timerStarted ,或
  • use an AtomicBoolean .使用AtomicBoolean

These ensure that there is a happens-before relation between a write of the flag by one thread and subsequent read by a second thread.这些确保一个线程写入标志和第二个线程随后读取之间存在发生在之前的关系。 That in turn guarantees that the read sees the value that was written.这反过来又保证读取看到写入的值。

"Why does not having System.out.println() makes me unable to break out of the loop?" “为什么没有 System.out.println() 使我无法跳出循环?”

Because there is no happens-before relationship;因为没有发生之前的关系; see above.看上面。

A more interesting question is:一个更有趣的问题是:

"Why does it work when you add the println?" “为什么添加 println 时它会起作用?”

The System.out.println() does some synchronization under the hood. System.out.println()System.out.println()做了一些同步。 (The stream object is thread-safe, and synchronizes to ensure that its data structures don't get messed up.) This is apparently sufficient to cause writes to timerStarted to be flushed to main memory. (流对象是线程安全的,并进行同步以确保其数据结构不会被弄乱。)这显然足以导致对timerStarted写入刷新到主内存。

However, this is not guaranteed.但是,这并不能保证。 The javadocs do not specify the synchronization behavior for the println call. javadocs 没有指定println调用的同步行为。 Furthermore, it is not clear that this is sufficient to give a (serendipitous!) happens before between the write and read of timerStarted .此外,尚不清楚这是否足以在timerStarted的写入和读取之间发生(偶然!)。

References:参考:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM