Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread continues to run after run() method

I have an issue with playing sound in my game. When the Thread that handles the sound playback exits it's run method it doesn't terminate/end/stop. I know it's this method that causes the problem, since when I comment the whole thing away no more Threads get created. (Checked with JVisualVM). The problem is that Threads do not get terminated after exiting the run method. I've placed a print command to ensure that it actually reaches the end of the run() method, and it always does.

However, when I check the process with JVisualVM, the thread count grows by 1 for each sound played. I also noted that the number of daemon threads is increased by 1 for each sound played. I am not sure what daemon threads are and how they work, but I've tried to kill the Thread in a number of ways. Including Thread.currentThread .stop() .destroy() .suspend() .interrupt() and returning from the run() method by return;

While writing this message I realised I need to close the clip object. This resulted in no extra threads being created and sustained. However, now the sound sometimes disappears and I have no idea why. Right now, I can choose between having sound in parallel and see my cpu get overloaded by an endless number of threads or have the sounds end abruptly whenever a new sound is played.

If anyone knows of a different approach of playing multiple sounds in parallel or knows what's wrong with my code, I would greatly appreciate any help.

Here is the method:

public static synchronized void playSound(final String folder, final String name) {
        new Thread(new Runnable() { // the wrapper thread is unnecessary, unless it blocks on the Clip finishing, see comments
            @Override
            public void run() {
                Clip clip = null;
                AudioInputStream inputStream = null;
                try{
                    do{
                        if(clip == null || inputStream == null)
                                clip = AudioSystem.getClip();
                                inputStream = AudioSystem.getAudioInputStream(SoundP.class.getResource(folder + "/" + name));
                        if(clip != null && !clip.isActive())
                                inputStream = AudioSystem.getAudioInputStream(SoundP.class.getResource(folder + "/" + name));
                                clip.open(inputStream);
                                clip.start(); 
                    }while(clip.isActive());
                    inputStream.close();
                } catch (LineUnavailableException e) {
                    e.printStackTrace();
                } catch (UnsupportedAudioFileException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
like image 284
Simon Eliasson Avatar asked May 15 '26 19:05

Simon Eliasson


1 Answers

A few things about Java Threads:

  • A Thread always dies when exiting it's Run() method. In your case, other threads are created inside the methods you called, but your thread ends (you can check it by naming it and see when it dies).
  • Never kill a thread using .stop(), .destroy() or .suspend(). These methods are deprecated and should not be used. Instead, you should basically get to the end of the Run() method. That's what Thread.interrupt() is for, but you'll have to support interrupting your thread by checking the Thread.isInterrupted() flag and then throwing InterruptedException and handling it (for more details see How to Stop a Thread).
  • "A daemon thread is a thread, that does not prevent the JVM from exiting when the program finishes but the thread is still running".

A few things about your code:

  • You have missing curly braces as mentioned by many users
  • I didn't quite understand what you're trying to achieve, but the do-while loop seems redundant. There are other good ways to wait for the sound to finish playing (if that's your goal), and a loop is not one of them. A while loop running many times without Sleeping, eats up your CPU for no good reason.
  • You should Close() (and Stop()) the Clip as you mentioned, in order to free system resources.

A working example with debug notes:

Try running this code, and see if it meets your requirements. I've added some thread methods calls and some System.out.prints for you to see when every bit of code happens. Try playing with tryToInterruptSound and mainTimeOut to see how it effects the output.

import java.io.File;
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.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public class PlaySound {
    private static boolean tryToInterruptSound = false;
    private static long mainTimeOut = 3000;
    private static long startTime = System.currentTimeMillis();

    public static synchronized Thread playSound(final File file) {

        Thread soundThread = new Thread() {
            @Override
            public void run() {
                try{
                    Clip clip = null;
                    AudioInputStream inputStream = null;
                    clip = AudioSystem.getClip();
                    inputStream = AudioSystem.getAudioInputStream(file);
                    AudioFormat format = inputStream.getFormat();
                    long audioFileLength = file.length();
                    int frameSize = format.getFrameSize();
                    float frameRate = format.getFrameRate();
                    long durationInMiliSeconds = 
                            (long) (((float)audioFileLength / (frameSize * frameRate)) * 1000);

                    clip.open(inputStream);
                    clip.start();
                    System.out.println("" + (System.currentTimeMillis() - startTime) + ": sound started playing!");
                    Thread.sleep(durationInMiliSeconds);
                    while (true) {
                        if (!clip.isActive()) {
                            System.out.println("" + (System.currentTimeMillis() - startTime) + ": sound got to it's end!");
                            break;
                        }
                        long fPos = (long)(clip.getMicrosecondPosition() / 1000);
                        long left = durationInMiliSeconds - fPos;
                        System.out.println("" + (System.currentTimeMillis() - startTime) + ": time left: " + left);
                        if (left > 0) Thread.sleep(left);
                    }
                    clip.stop();  
                    System.out.println("" + (System.currentTimeMillis() - startTime) + ": sound stoped");
                    clip.close();
                    inputStream.close();
                } catch (LineUnavailableException e) {
                    e.printStackTrace();
                } catch (UnsupportedAudioFileException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    System.out.println("" + (System.currentTimeMillis() - startTime) + ": sound interrupted while playing.");
                }
            }
        };
        soundThread.setDaemon(true);
        soundThread.start();
        return soundThread;
    }

    public static void main(String[] args) {
        Thread soundThread = playSound(new File("C:\\Booboo.wav"));
        System.out.println("" + (System.currentTimeMillis() - startTime) + ": playSound returned, keep running the code");
        try {   
            Thread.sleep(mainTimeOut );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (tryToInterruptSound) {
            try {   
                soundThread.interrupt();
                Thread.sleep(1); 
                // Sleep in order to let the interruption handling end before
                // exiting the program (else the interruption could be handled
                // after the main thread ends!).
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("" + (System.currentTimeMillis() - startTime) + ": End of main thread; exiting program " + 
                (soundThread.isAlive() ? "killing the sound deamon thread" : ""));
    }
}
  • playSound runs on a daemon thread, so that when the main (and only non-daemon) Thread ends, it stops.
  • I have calculated the sound file length according to this guy, so that I know in advanced how long to keep playing the Clip. This way I can let the Thread Sleep() and don't use the CPU. I use an additional isActive() as a test to see if it really ended, and if not - calculate the remaining time and Sleep() again (the sound will probably still be playing after the first Sleep due to two facts: 1. the length calculation doesn't take microseconds into consideration, and 2. "you cannot assume that invoking sleep will suspend the thread for precisely the time period specified").
like image 101
Elist Avatar answered May 17 '26 08:05

Elist



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!