没有打印声明的代码没有执行

我一直在制作倒计时节目,我想出了这个。

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

在最后的while循环中,如果没有print / println语句,倒计时代码就不会执行。 怎么会? 尽管如此,该程序与print语句完美配合。

首先,您的程序是线程不安全的,因为boolean started是一个共享变量 ,但它既volatile ,也不在synchronized块中访问。

现在,意外地, PrintStream#print是一种同步方法,并且在任何实际架构中,使用内存屏障 CPU指令实现进入和退出同步块,这导致线程本地状态和主内存之间的完全同步。

因此, 通过纯粹的意外 ,添加print调用允许一个线程(EDT)的started标志的设置被另一个(主线程)可见。

您的Swing应用程序设计很差。

  1. 不要在run()方法中使用while(true)循环。 阅读更多关于Swing中的Concurency的信息 。
  2. ListenersActionListener eg)的帮助下调用事件而不是标志( started这里started )。
  3. 而不是计算时间使用Swing Timer

像下一个一样更改run()方法:

 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()方法:

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

并在main方法中删除cake.run()

看,我做了一个SSCCE再现这种行为。 这是一个非常好的问题。

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

这打印出:“我完成了我的工作”。 而已。 添加volatile关键字实际上可以解决问题。

对我来说,看起来第二个线程没有得到关于更新的通知,因为他太忙了。

我猜测你的忙等待循环正在占用CPU,因此严重无法做任何事情。 print语句引起了足够的线程上下文切换,它可以完成其他工作。

编辑:好的,我做了一点测试。 我能够在HotSpot Server VM上重现OP的问题。 使用Thread.currentThread().setPriority(Thread.MIN_PRIORITY); 没有解决它,所以它不是一个饥饿问题。 @MarkoTopolnik建议,将变量设置为volatile为@MartinCourteau,确实修复了它。 那讲得通。 我原本无法在HotSpot客户端虚拟机上重现该问题; 显然,它的优化太弱,无法缓存已started变量。

(尽管如此,如果Java音频线程的线程优先级低于正常线程并且它是单CPU系统,那么饥饿就是一个看似合理的假设。)