2

我正在尝试在按钮按下或从列表中选择时播放 mp3 文件(我已成功管理)。但是,我似乎无法停止在同一个按钮按下时多次播放歌曲。

我想做的是在新线程中播放歌曲,禁用再次播放歌曲,直到线程关闭,然后再次允许播放。

我的代码如下:

public class SoundFactory {

private Player player;
private static boolean running = false;

private String getFile(String name) {
    String f = "sound" + File.separator + name + ".mp3";
    return f;
}

public void playMP3(String name) {


    if (!running) {
        running = true;

        try {
            FileInputStream fis = new FileInputStream(getFile(name));
            BufferedInputStream bis = new BufferedInputStream(fis);
            player = new Player(bis);
        } catch (Exception e) {
            System.out.println("Problem playing file " + name);
            System.out.println(e);
        }

        // run in new thread to play in background
        new Thread() {
            public void run() {
                try {
                    player.play();
                } catch (Exception e) {
                    System.out.println(e);
                }
            }
        }.start();
        //running = false;
    }
}


public void close() {
    if (player != null) player.close();
}

}

该文件通过以下方式播放:

    SoundFactory sf = new SoundFactory();
    sf.playMp3("song name");

在 JButton 点击

我是线程新手,所以如果这有一个明显的解决方案,我提前道歉!

4

2 回答 2

1

在我看来,您一次触发了多个点击事件,而不仅仅是一个。一点日志记录应该可以验证这一点。您的方法是对竞争条件开放的。

这两个事件可以如此接近,以至于当一个检查运行它时,它会看到 !running 为真。在此之前可以执行 running = true,第二个事件也将 !running 视为 true 并进入 if 子句。然后他们都将 running 设置为 true 并产生一个线程来播放 mp3。

您需要做的是使您的方法同步。

public synchronized void playMP3(String name)

http://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

如果 count 是 SynchronizedCounter 的一个实例,那么使这些方法同步有两个效果:

  • 首先,同一对象上的同步方法的两次调用不可能交错。当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程都会阻塞(暂停执行),直到第一个线程处理完该对象。
  • 其次,当同步方法退出时,它会自动与任何后续对同一对象的同步方法调用建立起之前的关系。这保证了对象状态的更改对所有线程都是可见的。

只是为了澄清我的最后一条评论,这是一个测试程序,显示了 running = false 应该放置的位置。

public class Test {

  public static boolean running = false;

  public synchronized void runner() {
    if(!running) {
      running = true;
      System.out.println("I'm running!");
      new Thread() {
        public void run() {
          for(int i=0; i<10000; i++) {} // Waste some time
          running = false; // This is only changed once the thread completes its execution.
        }
      }.start();
    } else {
      System.out.println("Already running.");
    }
  }

  public static void main(String[] args) {
    Test tester = new Test();
    tester.runner();
    tester.runner(); // The loop inside the Thread should still be running so this should fail.
    for(int i=0; i<20000; i++) {} // Waste even more time.
    tester.runner(); // The loop inside the Thread should be done so this will work.
  }
}

它输出:

I'm running! 
Already running. 
I'm running!

自从我使用 Swing 已经有好几年了,忘记了它的事件调度器是单线程的。所以你的问题更可能是这个而不是竞争条件。从一开始就开始编写线程安全的东西仍然没有什么坏处,因为它会让你习惯它并以这种方式思考。

使用同步方法的明确警告......如果只需要同步您的方法的一小部分,那么性能可能会很糟糕。在这种情况下,您的整个方法需要是线程安全的。

如果只有一小部分需要线程安全,则需要使用同步块。

每个实例的线程安全:

public class myClass {
   public void myFunc() {
      // bunch of code that doesn't need to be thread safe.
      synchronized(this) {
         // Code that needs to be thread safe per instance
      }
      // More code that doesn't need thread safety.
   }
}

跨所有实例的线程安全。

public class myClass {
   static Object lock = new Object();
   public void myFunc() {
      // bunch of code that doesn't need to be thread safe.
      synchronized(lock) {
         // Code that needs to be thread safe across all instances.
      }
      // More code that doesn't need thread safety.
   }
}

静态方法中的线程安全。

public class myClass {
   public static void myFunc() {
      // bunch of code that doesn't need to be thread safe.
      synchronized(MyClass.class) {
         // Code that needs to be thread safe.
      }
      // More code that doesn't need thread safety.
   }
}

可能比您想要的信息要多得多,但是我刚刚看到线程编程的教学如此糟糕很多很多次。

于 2013-05-17T19:15:26.643 回答
0

您需要JButton.setEnabled(false);在开始播放 mp3 之前立即调用,然后JButton.setEnabled(true);在 mp3 播放完毕时调用。

显然,您应该替换JButton为按钮的对象(例如:)playButton.setEnabled()

于 2013-05-17T18:31:22.493 回答