Code does not execute without print statement

I am doing 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 is not executed without the print / println statement inside. How so? The program works great with print instructions. A.

+6
source share
4 answers

First of all, your program is unsafe, since boolean started is a shared variable, but it is neither volatile nor available in synchronized blocks.

Now, by chance, PrintStream#print is a synchronized method, and in any real architecture, input and output from a synchronized block is implemented using CPU memory instructions, which cause complete synchronization between the local state of the stream and main memory.

Therefore, by pure chance, adding a print call allows you to set the started flag to one thread (EDT), which will be visible to the other (main thread).

+12
source

You have a bad design for your Swing app.

  • Do not use the while(true) in the run() method. Read more about Swing compatibility .
  • Raise events using Listeners ( ActionListener for example.) Instead of flags ( started here).
  • Instead of counting time, use Swing Timer .

Modify your run() method as follows:

 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 delete cake.run() in main .

+4
source

Look, I did SSCCE by reproducing this behavior. This 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!!"); } } } } 

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

It seems to me that the second thread does not receive a notification about the upgrade to started , because it is too loaded.

+2
source

I would suggest that your busy-wait cycle will slow down the processor so much that it can do nothing. The print statement invokes a fairly contextual flow switch that allows you to do other work.

Edit: Ok, I checked a little. I was able to reproduce the OP problem on VM HotSpot Server. Using Thread.currentThread().setPriority(Thread.MIN_PRIORITY); not fix, so this is not a hunger problem. Setting the variable to volatile , like @MartinCourteau, suggested @MarkoTopolnik to fix it. It makes sense. I could not initially reproduce the problem on the HotSpot client VM; apparently its optimizations are too weak for caching the started variable.

(However, if the Java audio stream had lower than usual stream priority and it was a single processor system, starvation was a plausible hypothesis.)

-1
source

All Articles