这个战舰游戏是我尝试编写的第一个多线程应用程序,它完美运行直到我添加了多线程,这只用于音效。它只在一个单独的类中,即AudioManager。
我很确定我缺乏关于并发性的经验和/或理解,即使我已经阅读了java教程等等。我想我只需要一些帮助就可以点击它。
无论如何,游戏运行良好,直到播放了足够的声音,内存不足并给我这个错误:
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: unable to create new native thread
我正在为每个声音效果创建一个新线程,因为我不希望gui等待声音完成,并且因为声音经常彼此非常接近而且我不想要它们如果它们重叠,则在同一个线程上发生冲突。我认为问题在于,我不确定在播放声音后如何关闭每个线程而不会使主线停止。
这是包含所有声音代码的类。使用setSound()方法播放声音,该方法设置要播放的声音,然后使用SoundPlayer内部类为Runnable启动新线程。提前致谢:
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
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.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
public class AudioManager {
private static Thread backgroundThread = new Thread();
private static int loopCounter = 2;
private static Clip clip;
private static String[] backgroundFiles = {
"/40_Avalon.wav","/13_Glatisant.wav",
"/31_Lying_In_Deceit.wav","/43_Return_to_Base.wav"};
private static String[] files = {
"/bigboom.wav","/Robot_blip.wav",
"/battleStations.WAV","/beep1.wav",
"/button-47.wav","/button-35.wav",
"/beep-23.wav","/Sonar_pings.wav",
"/button-21.wav","/SONAR.WAV"};
private static AudioInputStream currentBackgroundMusic;
private static AudioInputStream currentSound;
private static boolean backgroundOn = false;
private static boolean canStart = true;
private static AudioInputStream loadSound(int s){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource(files[s]));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
private static AudioInputStream loadBackground(int s){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource(backgroundFiles[s]));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
public static void setSound(int s){
currentSound = loadSound(s);
Thread thread = new Thread(new SoundPlayer());
thread.start();
}
private static void continueMusic(){
setBackgroundMusic(loopCounter);
loopCounter++;
if(loopCounter > 3) loopCounter = 0;
}
public static void playSound(){
try {
clip = AudioSystem.getClip();
clip.open(currentSound);
} catch (LineUnavailableException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
clip.start();
}
public static void setBackgroundMusic(int s){
if (backgroundOn) {
backgroundOn = false;
canStart = false;
try {
backgroundThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
currentBackgroundMusic = loadBackground(s);
backgroundThread = new Thread(new MusicPlayer());
backgroundOn = true;
backgroundThread.start();
canStart = true;
}
private static void playSound2(AudioInputStream audio) {
AudioFormat audioFormat = audio.getFormat();
SourceDataLine line = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class,audioFormat);
try{
line = (SourceDataLine) AudioSystem.getLine(info);
line.open(audioFormat);
}
catch (Exception e)
{
e.printStackTrace();
}
line.start();
int nBytesRead = 0;
byte[] abData = new byte[128000];
while (nBytesRead != -1 && backgroundOn)
{
try{
nBytesRead = audio.read(abData, 0, abData.length);
} catch (IOException e){
e.printStackTrace();
}
if (nBytesRead == -1) break;
line.write(abData, 0, nBytesRead);
}
line.drain();
line.stop();
line.close();
line = null;
backgroundOn = false;
}
private static class MusicPlayer implements Runnable{
@Override
public void run() {
playSound2(currentBackgroundMusic);
}
}
private static class SoundPlayer implements Runnable{
@Override
public void run() {
playSound();
}
}
public static void loopMusic(){
Thread loop = new Thread(new Runnable(){
@Override
public void run() {
while(true){
if(backgroundThread.isAlive()){
try {
backgroundThread.join(0);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if (canStart){
continueMusic();
}
}
}});
loop.start();
}
public static void reset(){
loopCounter = 2;
}
}
答案 0 :(得分:1)
首先,感谢所有发布答案的人。你们都帮助了很多,解决方案是你的答案的组合。我已经决定用我提出的解决方案发布我自己的答案,以便其他可能遇到同样问题的人受益。
事实证明,我确实创建了太多线程,操作系统只允许Java拥有一定的内存空间。所以我通过使用ExecutorService修复了这个问题。
然而,即使我没有明确地创建大量新线程,我仍然遇到同样的问题。为什么?因为我正在制作新的剪辑来播放声音。
我认为Clips以某种方式创建了播放声音的线程,因此他们可以在不锁定程序或GUI的情况下玩游戏(我以前没有理解)。因此,为了一劳永逸地解决问题,并且允许我的游戏连续快速播放完全相同的声音而不剪辑或不必等待之前的声音完成,我摆脱了执行者并创建了十个剪辑每一个声音都是。
当播放声音时,它会增加一个索引,以便下次播放该声音时,它实际上会使用不同的剪辑(但加载了相同的精确声音)并准备播放下一个剪辑。 / p>
我的游戏不再创建过多的线程或剪辑,并且运行得很好!下面是更新的代码,以及我用来了解发生情况的几个测试:
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
public class AudioManager {
private static ExecutorService backgroundPool = Executors.newFixedThreadPool(1);
private static Future<?> backgroundStatus;
private static int loopCounter = 2;
private static String[] backgroundFiles = {
"/40_Avalon.wav","/13_Glatisant.wav",
"/31_Lying_In_Deceit.wav","/43_Return_to_Base.wav"};
private static String[] files = {
"/bigboom.wav","/Robot_blip.wav",
"/battleStations.WAV","/beep1.wav",
"/button-47.wav","/button-35.wav",
"/beep-23.wav","/Sonar_pings.wav",
"/button-21.wav","/SONAR.WAV"};
private static AudioInputStream currentBackgroundMusic;
private static boolean backgroundOn = false;
private static boolean canStart = true;
private static int[] clipIndex = new int[10];
private static Clip[][] clips = new Clip[10][10];
private static void initializeClips(int sound){
clipIndex[sound] = 0;
for (int i = 0 ; i < 10 ; i++)
try {
clips[sound][i] = AudioSystem.getClip();
clips[sound][i].open(loadSound(sound));
clips[sound][i].addLineListener(new LineListener(){
@Override
public void update(LineEvent event) {
if(event.getType() == javax.sound.sampled.LineEvent.Type.STOP){
clips[sound][clipIndex[sound]].setFramePosition(0);
}
}});
} catch (LineUnavailableException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static AudioInputStream loadSound(int s){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource(files[s]));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
private static AudioInputStream loadBackground(int s){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource(backgroundFiles[s]));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
public static void setSound(int s){
if(clips[s] == null){
initializeClips(s);
}
playSound(s);
}
private static void continueMusic(){
setBackgroundMusic(loopCounter);
loopCounter++;
if(loopCounter > 3) loopCounter = 0;
}
public static void playSound(int sound){
if(clips[sound][0] == null){
initializeClips(sound);
}
clips[sound][clipIndex[sound]].start();
clipIndex[sound]++;
if(clipIndex[sound] == 10){
clipIndex[sound] = 0;
}
clips[sound][clipIndex[sound]].drain();
clips[sound][clipIndex[sound]].flush();
clips[sound][clipIndex[sound]].setFramePosition(0);
}
public static void setBackgroundMusic(int s){
canStart = false;
if (backgroundOn) {
backgroundOn = false;
}
currentBackgroundMusic = loadBackground(s);
backgroundStatus = backgroundPool.submit(new MusicPlayer());
canStart = true;
}
private static void playSound2(AudioInputStream audio) {
backgroundOn = true;
AudioFormat audioFormat = audio.getFormat();
SourceDataLine line = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class,audioFormat);
try{
line = (SourceDataLine) AudioSystem.getLine(info);
line.open(audioFormat);
}
catch (Exception e)
{
e.printStackTrace();
}
line.start();
int nBytesRead = 0;
byte[] abData = new byte[128000];
while (nBytesRead != -1 && backgroundOn){
try{
nBytesRead = audio.read(abData, 0, abData.length);
} catch (IOException e){
e.printStackTrace();
}
if (nBytesRead == -1) break;
line.write(abData, 0, nBytesRead);
}
line.drain();
line.stop();
line.close();
line = null;
backgroundOn = false;
}
private static class MusicPlayer implements Runnable{
@Override
public void run() {
playSound2(currentBackgroundMusic);
}
}
public static void loopMusic(){
Thread loop = new Thread(new Runnable(){
@Override
public void run() {
while(true){
if(backgroundStatus.isDone() && canStart){
continueMusic();
}
}
}});
loop.start();
}
public static void reset(){
loopCounter = 2;
}
}
以下是一个测试,它将告诉您操作系统允许JVM创建多少个线程。一旦出现错误,只需查看打印到控制台的最后一个数字。
public class Test1 {
static long count = 0L;
public static void main(String[] args) {
while(true){
count ++;
System.out.println(count);
new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}}).start();
}
}
}
除了创建剪辑和打开资源之外,以下是执行相同操作的测试。请注意,剪辑本身并不需要线程,但是一旦打开它们就可以了。在每次测试错误之前,您应该获得相同的数字(或关闭)。当然,您必须提供自己的声音文件来运行第二个声音文件。
import java.io.IOException;
import java.util.ArrayList;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import audio.AudioManager;
public class Test2 {
static long count = 0L;
static ArrayList<Clip> clips = new ArrayList<>();
public static void main(String[] args) {
while(true){
count ++;
System.out.println(count);
try {
Clip clip1 = AudioSystem.getClip();
clip1.open(loadSound());
clips.add(clip1);
} catch (LineUnavailableException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static AudioInputStream loadSound(){
AudioInputStream stream = null;
try {
stream = AudioSystem.getAudioInputStream(AudioManager.class.getClass().getResource("/bigboom.wav"));
} catch (UnsupportedAudioFileException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stream;
}
}
答案 1 :(得分:0)
好吧,正如你所说,你的问题是你的线程仍在运行或停止但是他们使用的内存并没有被java的垃圾收集器(GC)释放。
当run()方法返回(完成)或抛出异常时,线程停止。如果发生这种情况,并且代码中的任何地方都没有该线程的参考,最终它将被GC删除(例如,如果您的程序需要一些内存)。
首先,检查您创建的那些线程是否处于令人讨厌的无限循环中,如下所示:
public void run() {
while(true){
//...
//Some code
//...
}
}
并且一旦你确定它们在播放声音后正在以性质结束,请确保它们在代码中的任何其他地方都没有被引用(程序的一部分仍然保持指向该对象的指针)。
最后一点。考虑检查Thread Pool Pattern这类事情。我更喜欢为每个新任务创建一个新线程。它可以更简洁,更有效率。
答案 2 :(得分:0)
你需要摆脱所有这些静态变量。此代码不是线程安全的,因为您实际上是在尝试将所有静态变量用作共享状态。
我建议你开始在runnable对象周围传递状态。如果他们需要相互通信,请使用Java附带的内置并发实用程序。如果这还不够,请使用同步和变异对象状态。
粗略地看一下你的代码后,我可以看出你的代码会受到内存可见性问题的影响。您可以尝试通过使您的静态变量易变来解决此问题,但我怀疑它是否足够。如果将状态封装在单个对象中,它将使代码更清晰。
在你做任何进一步的事情之前,我会退后一步,尝试花费10-15分钟,然后想出一个整体设计。你将拥有什么样的对象,每个人有什么责任以及他们将拥有什么状态(不可变/可变)。这些对象将如何彼此同步(如果它们在不同的线程中运行?
答案 3 :(得分:0)
您的代码不是线程安全的。
等等,让我稍后再说。您的OOME是由创建的线程太多引起的。您目前的并发经验只是阅读在线教程,对吗?这些只涉及如何同步和共享,而不是线程的工作方式。线程有一个重要的设置和拆卸开销,你正在做的是无界线程创建,这意味着你只需创建越来越多的线程,直到系统不再支持它为止。< / p>
首先,您可以使用线程池修复问题,或者更具体地说,ExecutorService
可以同时执行任务。您只需要一个缓存的线程池。
其次,你有大量的共享状态字段。您可以通过在AtomicReference(或类似的东西)上的每个操作为单个状态快照创建不可变包装器来解决此问题,或者您可以同步对字段的访问。
第三,请摆脱所有静态字段和方法。虽然没有足够的代码来验证我的担忧,但我认为在这种情况下并不合适。
答案 4 :(得分:0)
“Clip.start()方法立即返回,系统在后台线程中播放声音文件。” (来自this question讨论了如何在彼此之后播放声音)。
由于你创建的线程有效地运行“make clip object并启动它”,它们实际上几乎没有做任何事情。甚至I / O操作(打开流)也是在正手(在主GUI线程中)完成的。
您认为GUI必须等待剪辑完成的假设似乎无效。我怀疑如果它们重叠,它们可能在同一个线程上发生冲突。您能否确认GUI对多线程的响应速度更快?我的猜测是它实际上响应性较差,因为创建和启动新线程并不便宜。