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版本有时并不精确,因为它试图成为一种更容易理解(简化)的读物。