Code not executed without a print statement [duplicate]

家住魔仙堡 提交于 2019-12-01 02:15:27

问题


i've been making a countdown program, and i came up with this.

package main;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

public class Gatoo extends JFrame implements ActionListener {
    private int sec, min, secTot, since = 999;
    private long lastTime;

    private JTextField mm = new JTextField(2), ss = new JTextField(2);
    private JLabel minLab = new JLabel("Minutes:"), secLab = new JLabel(
            "Seconds:");
    private JButton start = new JButton("Start");

    private Clip done;
    private boolean started = false;

    private static final long serialVersionUID = 4277921337939922028L;

    public static void main(String[] args) {
        Gatoo cake = new Gatoo("Title");
        cake.pack();
        cake.setSize(800, 600);
        cake.setLocationRelativeTo(null);
        cake.setDefaultCloseOperation(3);
        cake.setVisible(true);
        cake.run();
    }

    public Gatoo(String s) {
        super(s);
        setLayout(new FlowLayout());

        start.addActionListener(this);

        add(minLab);
        add(mm);
        add(secLab);
        add(ss);
        add(start);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        started = true;
    }

    public void play(File file) throws MalformedURLException,
            UnsupportedAudioFileException, IOException,
            LineUnavailableException {
        AudioInputStream ais = AudioSystem.getAudioInputStream(new File(
                "lib/done.wav"));
        DataLine.Info info = new DataLine.Info(Clip.class, ais.getFormat());
        done = (Clip) AudioSystem.getLine(info);
        done.open(ais);
        done.start();
    }

    public void run() {
        while (true) {
            System.out.print("");// needed?
            if (started) {
                try {
                    min = Integer.parseInt(mm.getText());
                    sec = Integer.parseInt(ss.getText());
                    secTot = (min * 60) + sec;
                    lastTime = System.currentTimeMillis();
                    while (secTot > 0) {
                        since = (int) (System.currentTimeMillis() - lastTime);
                        if (since > 998) {
                            lastTime = System.currentTimeMillis();
                            secTot--;
                        }
                    }

                    play(new File("done.wav"));

                } catch (NumberFormatException exception) {
                    System.out.println("Minutes and seconds must be numbers.");
                    return;
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
                started = false;
            }
        }
    }
}

In the while loop at the end the countdown code doesn't execute without a print / println statement inside. How come? The program works perfectly fine with the print statement though.


回答1:


First and foremost, your program is thread-unsafe because boolean started is a shared variable, but it is neither volatile nor accessed within synchronized blocks.

Now, accidentally, PrintStream#print is a synchronized method and, on any actual architecture, entering and exiting a synchronized block is implemented using memory barrier CPU instructions, which cause a complete synchronization between the thread-local state and main memory.

Therefore, by pure accident, adding the print call allows the setting of started flag by one thread (the EDT) to be visible by another (the main thread).




回答2:


You have poor design for Swing application.

  1. Don't use while(true) loop in your run() method. Read more about Concurency in Swing.
  2. Call events with help of Listeners(ActionListener e.g.) instead of flags(started here).
  3. Instead of counting time use Swing Timer.

Change your run() method like next:

public void run() {
      min = Integer.parseInt(mm.getText());
      sec = Integer.parseInt(ss.getText());
      secTot = (min * 60) + sec;
      Timer timer = new Timer(1000*secTot, new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
              try {
                play(new File("done.wav"));
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    });
      timer.start();
}

actionPerformed() method :

@Override
public void actionPerformed(ActionEvent e) {
    run();
}

and remove cake.run() in main method.




回答3:


Look, I made a SSCCE reproducing this behavior. It is a really good question.

public class ThreadRacing implements Runnable
{
    public boolean started = false;

    public static void main(String[] args)
    {
        new ThreadRacing().test();
    }

    public void test()
    {
        new Thread(this).start();
        try
        {
            Thread.sleep(1000);
        } catch (Exception e)
        {

        }
        started = true;
        System.out.println("I did my job");
    }

    @Override
    public void run()
    {
        while (true)
        {
            //System.out.print("");
            if (started)
            {
                System.out.println("I started!!");
            }
        }
    }

}

This prints: "I did my job". Nothing more. Adding a volatile keyword actually fixes the problem.

To me, it looks like the second Thread gets not notified about the update to started because he is too bussy.




回答4:


I would surmise that your busy-wait loop is hogging the CPU so severely it is unable to do anything. The print statement is causing just enough of a thread context switch that it is able to get other work done.

Edit: Okay, I did a little testing. I was able to reproduce OP's problem on the HotSpot Server VM. Using Thread.currentThread().setPriority(Thread.MIN_PRIORITY); did not fix it, so it is not a starvation issue. Setting the variable to volatile as @MartinCourteau, @MarkoTopolnik suggested, did fix it. That makes sense. I couldn't originally reproduce the problem on the HotSpot Client VM; apparently its optimizations are too weak for it to cache the started variable.

(Still, if the Java audio thread had a lower than normal thread priority and it were a single-CPU system, starvation was a plausible hypothesis.)



来源:https://stackoverflow.com/questions/20786483/code-not-executed-without-a-print-statement

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!