IllegalThreadStateException: Thread already started解决

本文详细解析了在Java编程中遇到IllegalThreadStateException的原因,并提供了两种有效的解决策略。通过新建线程或利用PriorityBlockingQueue,实现灵活的线程控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IllegalThreadStateException的原因解析

mThread = new MThread();
mThread.start();
mThread.interrupt();
mThread.start();

这段代码运行,就会出现上面的异常,从字面是理解也很容易理解:非法线程状态异常,线程已经start。

具体原因也很容易找到,看一下thread.start()里面做了什么:

    public synchronized void start() {
        checkNotStarted();

        hasBeenStarted = true;

        nativeCreate(this, stackSize, daemon);
    }
 private void checkNotStarted() {
        if (hasBeenStarted) {
            throw new IllegalThreadStateException("Thread already started");
        }
    }

在线程开始的时候会将hasBeenStarted设置成true,而在interrupt终止线程的时候,并没有将hasBeenStarted设置成false,所以在第二次start线程的时候会出现IllegalThreadStateException。


解决方法

  • new 一个新的thread
  • thread不终止

对于第一个方法,创建新的线程去执行,这个方法不建议使用,因为大量的线程会影响你应用的性能,而第二个方法不终止线程,听起来可行,但是有一些具体情况,必须需要我们终止我们的线程,有没有一种情况是在一个线程里面我们想什么时候让它运行就什么时候运行,不运行的时候等待。答案是有的——PriorityBlockingQueue。

BlockingQueue抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()pull()take()pull(time,unit)
检查element()peek()不可用不可用

- add(E e):把e加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则招聘异常。
- offer(E e):表示如果可能的话,将e加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false。
- put(E e):把e加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续。
- take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止

其中take方法取值失败后:阻断进入等待状态直到Blocking有新的对象被加入为止。

private final PriorityBlockingQueue<Long> mQueue =
            new PriorityBlockingQueue<>();
mQueue.add(delayTime);
 private class Dispatcher extends Thread {
        WeakReference<MainActivity> context;

        public Dispatcher(MainActivity activity) {
            context = new WeakReference<>(
                    activity);
        }
        @Override
        public void run() {
            super.run();
            long loadTime;
            while (true) {
                try {
                    loadTime = mQueue.take();
                } catch (Exception e) {
                    // We may have been interrupted because it was time to quit.
                    if (mQuit) {
                        return;
                    }
                    if (context == null || context.get() == null) {
                        return;
                    }
                    continue;
                }
                if (context == null || context.get() == null) {
                    return;
                }
                if (isStop.get()) {
                    return;
                }
                int i = mProgressBar == null ? 0 : mProgressBar.getProgress();
                for (; i < 100; ) {
                    if (isStop.get()) {
                        return;
                    }
                    if (context == null || context.get() == null) {
                        return;
                    }
                    i += ratio;
                }
            }
        }
    }

mQueue.take()取值失败后会阻塞,当我们需要线程开始运行的时候,只要向队列里面add值就可以了

<think>我们遇到`java.lang.IllegalThreadStateException:processhasnotexited`异常时,通常是因为在尝试获取进程的退出状态(通过`exitValue()`方法)时,该进程尚未终止。在Java中,`Process`对象代表一个正在执行的进程。`Process.exitValue()`方法会立即返回进程的退出值,但如果进程尚未终止,则会抛出`IllegalThreadStateException`。为了避免这个异常,我们应该使用`Process.waitFor()`方法,该方法会阻塞当前线程直到进程执行完成,然后返回退出值。以下我们将提供两种解决方案:1.使用`waitFor()`方法等待进程结束。2.使用带超时的`waitFor(longtimeout,TimeUnitunit)`方法(Java8+)。同时,我们也会处理进程的输出流和错误流,以避免进程阻塞(因为输出缓冲区可能被填满,导致进程挂起)。下面是一个改进的工具类方法,用于执行命令并获取结果,同时避免上述异常。###解决方案:安全执行外部进程```javaimportjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.util.concurrent.TimeUnit;publicclassProcessExecutor{/***执行命令并等待完成(无限等待)*@paramcommand命令数组*@return进程退出码*@throwsIOException如果发生I/O错误*@throwsInterruptedException如果进程被中断*/publicstaticintexecuteCommand(String[]command)throwsIOException,InterruptedException{Processprocess=Runtime.getRuntime().exec(command);//处理输出流和错误流,防止阻塞StreamGobbleroutputGobbler=newStreamGobbler(process.getInputStream());StreamGobblererrorGobbler=newStreamGobbler(process.getErrorStream());outputGobbler.start();errorGobbler.start();//等待进程结束returnprocess.waitFor();}/***执行命令并设置超时*@paramcommand命令数组*@paramtimeout超时时间*@paramunit时间单位*@return进程退出码(如果超时则返回-1)*@throwsIOException如果发生I/O错误*@throwsInterruptedException如果进程被中断*/publicstaticintexecuteCommandWithTimeout(String[]command,longtimeout,TimeUnitunit)throwsIOException,InterruptedException{Processprocess=Runtime.getRuntime().exec(command);StreamGobbleroutputGobbler=newStreamGobbler(process.getInputStream());StreamGobblererrorGobbler=newStreamGobbler(process.getErrorStream());outputGobbler.start();errorGobbler.start();//带超时的等待if(process.waitFor(timeout,unit)){returnprocess.exitValue();}else{//超时,销毁进程process.destroyForcibly();return-1;//或者抛出自定义异常}}//用于消费流的线程类privatestaticclassStreamGobblerextendsThread{privateInputStreaminputStream;publicStreamGobbler(InputStreaminputStream){this.inputStream=inputStream;}@Overridepublicvoidrun(){try(BufferedReaderreader=newBufferedReader(newInputStreamReader(inputStream))){Stringline;while((line=reader.readLine())!=null){//这里可以记录日志或者丢弃输出,根据需求调整//例如:System.out.println(line);}}catch(IOExceptione){e.printStackTrace();}}}}```###解释代码1.**`executeCommand`方法**:-使用`Runtime.getRuntime().exec()`启动进程。-创建两个`StreamGobbler`线程分别消费进程的标准输出流和错误输出流。这是因为如果不消费这些流,当缓冲区满时,进程可能会被阻塞。-调用`process.waitFor()`等待进程结束,并返回退出码。2.**`executeCommandWithTimeout`方法**:-与上面类似,但使用`process.waitFor(longtimeout,TimeUnitunit)`方法,该方法在指定的时间内等待进程结束。-如果超时,则强制销毁进程并返回-1(也可以选择抛出异常)。3.**`StreamGobbler`类**:-一个简单的线程类,用于读取并丢弃(或处理)进程的输出。如果不处理这些输出,当输出缓冲区满时,进程可能会挂起。###使用示例```javapublicstaticvoidmain(String[]args){String[]command={"ping","www.google.com"};try{intexitCode=ProcessExecutor.executeCommandWithTimeout(command,5,TimeUnit.SECONDS);System.out.println("Exitcode:"+exitCode);}catch(IOException|InterruptedExceptione){e.printStackTrace();}}```###注意事项-在调用`waitFor()`之前处理输出流,否则可能会导致进程阻塞。-对于长时间运行的进程,建议使用带超时的版本,避免永久等待。-强制销毁进程可能会导致资源未正确释放,应谨慎使用。###在之前的FFmpeg截帧工具中的改进我们可以修改之前的`VideoFrameExtractor`类,使用上述安全执行进程的方法。例如,在`getVideoDuration`方法中,可以替换为:```javaprivatestaticdoublegetVideoDuration(StringvideoPath)throwsIOException,InterruptedException{String[]command={FFPROBE_PATH,"-v","error","-show_entries","format=duration","-of","default=noprint_wrappers=1:nokey=1",videoPath};//使用带超时的执行intexitCode=ProcessExecutor.executeCommandWithTimeout(command,5,TimeUnit.SECONDS);if(exitCode!=0){thrownewIOException("FFprobe执行失败,退出码:"+exitCode);}//这里需要重新设计:如何获取输出?我们可以修改ProcessExecutor来捕获输出//由于需要输出,我们需要改进上面的ProcessExecutor,使其能够返回输出内容}```因此,我们需要增强`ProcessExecutor`来捕获输出。下面是一个改进版本:###增强版:捕获输出并支持超时```javaimportjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.util.concurrent.TimeUnit;publicclassProcessExecutorWithOutput{publicstaticclassProcessResult{privatefinalintexitCode;privatefinalStringoutput;privatefinalStringerror;publicProcessResult(intexitCode,Stringoutput,Stringerror){this.exitCode=exitCode;this.output=output;this.error=error;}publicintgetExitCode(){returnexitCode;}publicStringgetOutput(){returnoutput;}publicStringgetError(){returnerror;}}publicstaticProcessResultexecuteCommand(String[]command)throwsIOException,InterruptedException{Processprocess=Runtime.getRuntime().exec(command);//处理输出流和错误流Stringoutput=readStream(process.getInputStream());Stringerror=readStream(process.getErrorStream());intexitCode=process.waitFor();returnnewProcessResult(exitCode,output,error);}publicstaticProcessResultexecuteCommandWithTimeout(String[]command,longtimeout,TimeUnitunit)throwsIOException,InterruptedException{Processprocess=Runtime.getRuntime().exec(command);//启动读取流的线程StreamReaderoutputReader=newStreamReader(process.getInputStream());StreamReadererrorReader=newStreamReader(process.getErrorStream());outputReader.start();errorReader.start();if(process.waitFor(timeout,unit)){outputReader.join();//确保读取完成errorReader.join();returnnewProcessResult(process.exitValue(),outputReader.getResult(),errorReader.getResult());}else{process.destroyForcibly();outputReader.join();//仍然等待读取线程结束errorReader.join();returnnewProcessResult(-1,outputReader.getResult(),errorReader.getResult());}}privatestaticStringreadStream(InputStreaminputStream)throwsIOException{StringBuilderresult=newStringBuilder();try(BufferedReaderreader=newBufferedReader(newInputStreamReader(inputStream))){Stringline;while((line=reader.readLine())!=null){result.append(line).append("\n");}}returnresult.toString();}privatestaticclassStreamReaderextendsThread{privatefinalInputStreaminputStream;privateStringresult;publicStreamReader(InputStreaminputStream){this.inputStream=inputStream;this.result="";}publicStringgetResult(){returnresult;}@Overridepublicvoidrun(){try{result=readStream(inputStream);}catch(IOExceptione){result=e.getMessage();}}}}```###在`VideoFrameExtractor`中使用增强版```javaprivatestaticdoublegetVideoDuration(StringvideoPath)throwsIOException,InterruptedException{String[]command={FFPROBE_PATH,"-v","error","-show_entries","format=duration","-of","default=noprint_wrappers=1:nokey=1",videoPath};ProcessExecutorWithOutput.ProcessResultresult=ProcessExecutorWithOutput.executeCommandWithTimeout(command,5,TimeUnit.SECONDS);if(result.getExitCode()!=0){thrownewIOException("FFprobe执行失败:"+result.getError());}returnDouble.parseDouble(result.getOutput().trim());}```这样,我们就能够安全地执行外部进程,避免`IllegalThreadStateException`异常,同时还能处理超时和流数据。§§相关问题§§:1.在Java中,除了使用`Runtime.exec()`,还有哪些方式可以执行外部进程?2.如何处理外部进程的输入流(即向进程输入数据)?3.在Java中,如何异步执行外部进程并实时获取输出?4.在多线程环境下执行外部进程需要注意哪些问题?</think># 解决 `java.lang.IllegalThreadStateException: process has not exited` 异常 这个异常发生在尝试获取进程退出状态(`exitValue()`)时,但进程尚未完成执行。让我们深入分析原因并提供完整的解决方案。 ## 问题原因分析 `IllegalThreadStateException` 通常发生在以下情况: 1. 调用 `Process.exitValue()` 时进程仍在运行 2. 未正确处理进程的输出流,导致缓冲区填满后进程阻塞 3. 未使用 `waitFor()` 方法等待进程完成 4. 进程执行时间过长,但未设置超时处理 ## 完整解决方案 ### 1. 安全执行外部进程的通用工具类 ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.concurrent.TimeUnit; public class ProcessExecutor { /** * 安全执行外部命令 * @param command 命令数组 * @param timeout 超时时间(秒) * @return 包含退出码和输出的结果对象 * @throws IOException 如果发生I/O错误 * @throws InterruptedException 如果进程被中断 */ public static ProcessResult executeCommand(String[] command, long timeout) throws IOException, InterruptedException { Process process = new ProcessBuilder(command).start(); // 启动线程读取输出流和错误流 StreamConsumer outputConsumer = new StreamConsumer(process.getInputStream()); StreamConsumer errorConsumer = new StreamConsumer(process.getErrorStream()); outputConsumer.start(); errorConsumer.start(); // 等待进程完成(带超时) boolean completed = process.waitFor(timeout, TimeUnit.SECONDS); if (!completed) { // 超时处理 process.destroyForcibly(); throw new RuntimeException("命令执行超时: " + String.join(" ", command)); } // 确保消费线程完成 outputConsumer.join(1000); errorConsumer.join(1000); int exitCode = process.exitValue(); return new ProcessResult(exitCode, outputConsumer.getOutput(), errorConsumer.getOutput()); } /** * 流消费者线程 - 防止缓冲区填满导致进程阻塞 */ private static class StreamConsumer extends Thread { private final InputStream inputStream; private final StringBuilder output = new StringBuilder(); public StreamConsumer(InputStream inputStream) { this.inputStream = inputStream; } @Override public void run() { try (BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream))) { String line; while ((line = reader.readLine()) != null) { output.append(line).append(System.lineSeparator()); } } catch (IOException e) { // 忽略读取中的异常 } } public String getOutput() { return output.toString(); } } /** * 进程执行结果封装类 */ public static class ProcessResult { private final int exitCode; private final String output; private final String error; public ProcessResult(int exitCode, String output, String error) { this.exitCode = exitCode; this.output = output; this.error = error; } public int getExitCode() { return exitCode; } public String getOutput() { return output; } public String getError() { return error; } public boolean isSuccess() { return exitCode == 0; } } } ``` ### 2. 在 FFmpeg 截帧工具中使用安全执行器 ```java public class VideoFrameExtractor { // ... [其他代码不变] ... private static double getVideoDuration(String videoPath) throws IOException, InterruptedException { String[] command = { FFPROBE_PATH, "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", videoPath }; ProcessResult result = ProcessExecutor.executeCommand(command, 10); if (!result.isSuccess()) { throw new IOException("FFprobe执行失败: " + result.getError()); } return Double.parseDouble(result.getOutput().trim()); } private static void captureFrameAtTime(String videoPath, String outputPath, double time) throws IOException, InterruptedException { // ... [创建输出目录代码] ... String[] command = { FFMPEG_PATH, "-ss", String.valueOf(time), "-i", videoPath, "-vframes", "1", "-q:v", "2", outputPath }; ProcessResult result = ProcessExecutor.executeCommand(command, 30); if (!result.isSuccess() || !new File(outputPath).exists()) { throw new IOException("截帧失败: " + result.getError()); } } } ``` ## 关键解决方案详解 ### 1. 正确使用 `waitFor()` 方法 ```java boolean completed = process.waitFor(timeout, TimeUnit.SECONDS); ``` - 使用带超时的 `waitFor()` 方法替代直接调用 `exitValue()` - 设置合理的超时时间(根据命令执行时间预估) ### 2. 流处理机制 ```java // 启动线程读取输出流 StreamConsumer outputConsumer = new StreamConsumer(process.getInputStream()); outputConsumer.start(); ``` - 创建独立线程消费进程的输出流和错误流 - 防止输出缓冲区填满导致进程阻塞 - 保存输出内容用于错误诊断 ### 3. 超时处理 ```java if (!completed) { process.destroyForcibly(); throw new RuntimeException("命令执行超时: " + String.join(" ", command)); } ``` - 超时后强制终止进程 - 抛出有意义的异常信息 ### 4. 结果封装 ```java public static class ProcessResult { private final int exitCode; private final String output; private final String error; } ``` - 封装退出码、标准输出和错误输出 - 便于调用者统一处理结果 ## 在 Spring Boot 控制器中的使用示例 ```java @RestController public class VideoController { @PostMapping("/capture-frame") public ResponseEntity<?> captureFrame( @RequestParam("video") MultipartFile videoFile, @RequestParam(value = "outputDir", defaultValue = "./captures") String outputDir) { try { Path videoPath = Paths.get(outputDir, videoFile.getOriginalFilename()); videoFile.transferTo(videoPath); String outputImage = Paths.get(outputDir, "capture_" + System.currentTimeMillis() + ".jpg").toString(); VideoFrameExtractor.captureRandomFrame( videoPath.toString(), outputImage); // 返回图片文件 File file = new File(outputImage); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.IMAGE_JPEG); headers.setContentLength(file.length()); return new ResponseEntity<>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("截帧失败: " + e.getMessage()); } } } ``` ## 最佳实践建议 1. **始终使用 `waitFor()` 替代 `exitValue()`** - 前者会阻塞直到进程完成,后者只是检查当前状态 2. **处理所有进程输出流** - 不处理输出流可能导致进程阻塞 - 特别是当输出量较大时 3. **设置合理的超时时间** - 根据命令类型设置不同的超时值 - 视频处理通常比简单命令需要更长时间 4. **使用 ProcessBuilder 替代 Runtime.exec()** - ProcessBuilder 提供更精细的控制 - 更好的参数处理(避免空格问题) 5. **记录详细的错误信息** - 保存进程的退出码、标准输出和错误输出 - 便于诊断问题 6. **资源清理** - 确保关闭所有流 - 超时后强制终止进程
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值