Java 在没有打印语句的情况下无法执行代码

Java 在没有打印语句的情况下无法执行代码,java,swing,java-7,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.sample

我一直在做一个倒计时计划,我想到了这个

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语句配合得非常好。

我猜想,您的繁忙等待循环占用了CPU太多,以至于无法执行任何操作。print语句引起的线程上下文切换刚好足以完成其他工作

编辑:好的,我做了一些测试。我能够在HotSpot服务器VM上重现OP的问题。使用
Thread.currentThread().setPriority(Thread.MIN\u PRIORITY)没有修复它,因此它不是饥饿问题。@MarkoTopolnik建议,将变量设置为
volatile
,就像@martincourtau一样,确实解决了这个问题。这是有道理的。我最初无法在HotSpot客户端VM上重现该问题;显然,它的优化太弱,无法缓存
started
变量


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

Swing应用程序的设计很差

  • 不要在
    run()
    方法中使用
    while(true)
    循环。阅读更多关于
  • 使用
    侦听器
    ActionListener
    例如)而不是标志(
    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();
    }
    

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

    首先,您的程序是线程不安全的,因为
    boolean start
    是一个共享变量,但它既不是
    易失性的
    ,也不是在同步块中访问的

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


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

    看,我制作了一个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
    关键字实际上解决了这个问题


    在我看来,第二个线程似乎没有得到关于
    已启动更新的通知,因为他太忙碌了。

    这将打印它“”(空)。老实说,我不相信你。我认为你的测试在某种程度上是错误的。运行条件。
    println
    只是延迟代码,因此另一个线程有时间设置另一个条件(可能将
    started
    设置为true)。修改你的操作时间表。@SJuan76:我也这么认为,但他把他的代码放在了一个while-true块中(auch!)除此之外,为什么不使用一个Swing计时器,而不是尝试实现你自己的呢?@1:如果他的代码在一段时间内(true),你能解释一下这个答案实际上是如何有效的吗?@MartijnCourteaux这正是它有效的原因。while循环正在耗尽音频系统。在抢占式多任务系统上,你不能幼稚地耗尽线程。他实际上没有阻止EDT,因为他从未切换到主EDT。-1:他确实没有阻止EDT。主线程上有很多负载。是的,现在我注意到了这个事实。我把列表变成了有序列表。请检查它是否仍然符合您的预期含义(我没有填充)。1)我使用了while(true)循环,因为我希望倒数功能可以多次使用。2) 我使用了一个标志,因为当我在ActionListener中执行代码时,“开始”按钮被卡住了。3) 这实际上是一个好主意,在我编写代码时忘记了它。+1用于实际正确回答。一秒钟后,JIT编译器将获得
    run
    方法并重新编译它,以便将完整的
    if
    构造作为死代码消除。它有权这样做,因为
    started
    是非易失性的,并且不能在
    while(true)
    循环中更改。因此,这并不能真正证明线程处于忙碌状态——如果这是真的,那么让变量
    变为volatile
    不会让它们变得更忙碌。你是什么意思?一秒钟后,JIT编译器将到达run方法并重新编译它。你确定你看对了我的代码吗?好吧,当JVM处于调试模式时,根本没有编译。否则,单步执行代码、断点和几乎所有其他操作都是不可能的,您可以将编译阈值设置为1,JIT编译器仍然可以优化
    if
    ,因为整个
    run
    方法根本不涉及线程间操作(即当
    if
    条件计算为
    false
    时)。因此,编译器可以假设执行
    run
    的线程是唯一的线程。更妙的是,杰里米·曼森(Jeremy Manson)等人认为,JLS版本有时并不精确,因为它试图成为一种更容易理解(简化)的读物。