没有print语句就不能执行代码

时间:2013-12-26 14:27:52

标签: java swing java-7

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

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语句完美配合。

4 个答案:

答案 0 :(得分:12)

首先,您的程序线程不安全,因为boolean started共享变量,但它既不是volatile也不是{同步块。

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

因此,纯事故,添加print调用允许一个线程(EDT)设置started标志被另一个线程(主线程)看到)。

答案 1 :(得分:4)

Swing应用程序的设计很差。

  1. 请勿在{{1​​}}方法中使用while(true)循环。详细了解Concurency in Swing
  2. run()(例如Listeners)的帮助下呼叫事件,而不是标记(ActionListener此处)。
  3. 而不是计算时间使用Swing Timer
  4. 更改您的started方法,如下一步:

    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(); } 方法中的cake.run()

答案 2 :(得分:2)

看,我做了一个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关键字实际上可以解决问题。

对我来说,看起来第二个帖子没有收到有关started更新的通知,因为他太忙了。

答案 3 :(得分:-1)

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

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

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