问题
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.
- Don't use
while(true)
loop in yourrun()
method. Read more about Concurency in Swing. - Call events with help of
Listeners
(ActionListener
e.g.) instead of flags(started
here). - 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