【问题标题】:Kill or stop a endless/not-returning-value thread in Java杀死或停止 Java 中的无限/不返回值线程
【发布时间】:2014-08-18 11:02:51
【问题描述】:

我有一个 Java 函数。它通常在完成任务后返回一个值。但是,在某些情况下,它什么也不返回。我创建了一个可运行文件并将此函数作为线程运行。但是,由于它没有返回值,因此虽然它完成了它的任务,但它并没有完成。该进程保持活动状态,因为它等待返回值。有没有办法在触发后或超时后杀死该线程? Stop() 或 Destroy() 不起作用。在调试期间,线程被认为是活动的,我希望它被双向删除/删除

Runnable runnable = new Runnable() {
            @Override
            public void run() {
               int stat = RunMyFunction();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();

【问题讨论】:

  • 拜托,请检查 RunMyFunction 的内容,因为不清楚究竟是什么问题?作为提示,我建议使用中断机制来停止线程。
  • 在某些情况下它什么也不返回,除非您的意思是方法执行因异常而中止,否则我几乎无法相信。
  • 是的,很难相信,但我正在使用硬件的 wsdl 库函数,不幸的是它的工作原理就是这样。
  • 考虑使用 ExecuterService 使线程超时。 docs.oracle.com/javase/6/docs/api/java/util/concurrent/…

标签: java multithreading runnable kill


【解决方案1】:

Java 不支持通过 java.lang.Thread 上的任何方法杀死线程。

stop() 和 destroy() 乍一看确实很有希望,但它们都已被弃用。

销毁状态的文档:

这个方法最初是为了在不进行任何清理的情况下销毁这个线程而设计的。但是,该方法从未实施。如果要实现的话,就会很容易死锁

然后停止:

这种方法本质上是不安全的。使用 Thread.stop 停止线程会导致它解锁所有已锁定的监视器(这是未检查的 ThreadDeath 异常在堆栈上传播的自然结果)。如果以前受这些监视器保护的任何对象处于不一致状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。

因此,当文档说“已弃用”时,真正的意思是它们已损坏且绝不能使用!?! Java API 设计者在其 API 的向后兼容性方面投入了大量工作,其他语言会删除这些方法,Sun 决定保留它们,因为它们的内部指南(无论对错)不允许删除公共 api 方法。

所以,问题仍然存在。如何让一个线程从另一个线程退出?可悲的是,必须不遗余力地轮询退出变量。这可以是自定义变量,也可以是 java.lang.Thread 中的标准标志,可通过“interrupted()”访问。使用 interrupted() 的优点是其他 Java API(例如 IO)在阻塞 API 调用期间支持此标志,并且将退出并抛出 InterruptedException。调用中断()的检测不是立即的,因为它设置一个标志并依赖线程在将来的某个时间轮询变量。

Oracle 提供了有关如何使用中断here 进行编码的教程。

【讨论】:

    【解决方案2】:

    您遇到的真正问题是RunMyFunction 有时永远不会终止。正如其他人已经说过的那样,在 Java 中不打算杀死线程,因此没有好的方法可以做到这一点。相反,您应该推理为什么要调用可能非终止的方法。这看起来像代码气味。执行以下操作:

    如果您是RunMyFunction 的作者,请确保它始终终止它可以被中断。您可以通过检查Thread.currentThread().isInterrupted()throw 以及InterruptedException 来做到这一点。例如:

    void run(){
    
        while(...){ // this loop sometimes runs forever
           if(Thread.currentThread().isInterrupted())
               throw new InterruptedException(); // Now, we can "kill" this thread here
        } 
    
    }
    

    【讨论】:

      【解决方案3】:

      使用 ExecuterService 可以指定超时。

      ExecutorService executor = Executors.newFixedThreadPool(1);
          List<Callable<String>> tasks = new ArrayList<Callable<String>>();
      
          tasks.add(new Callable<String>() {
      
              @Override
              public String call() throws Exception {
                   int stat = RunMyFunction();
                   return "Execution Finished";
              }
          });
      
          new Thread(new Runnable() {
      
              @Override
              public void run() {
                  try {
                      executor.invokeAll(tasks, 10, TimeUnit.SECONDS);
                  } catch (InterruptedException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
      
              }
          }).start();
      

      invokeAll(...) 是一个阻塞调用,所以我在一个新线程中添加了。

      【讨论】:

      • ExecutorService 没有任何杀死线程的魔法。该任务将继续运行。
      • 我已经编辑了代码。实际上,当我使用 System.out 在 call() 方法中使用无限循环测试代码时,系统输出在 10 秒后停止。不是说任务完成了吗?
      • 我敢打赌线程池线程是一个守护线程,所以它不会阻止 JVM 终止。
      • @aioobe 我认为你是对的,我会在对该主题进行一些研究后删除这个答案。
      【解决方案4】:

      解决方案一:定时运行:如果希望某个方法在指定时间后返回或抛出异常,请使用以下方法在后台线程上执行该方法,同时等待它完成:

      public static void timedRun(Runnable r, long timeout, TimeUnit unit)
          throws InterruptedException, TimeoutException {
      
          Future<?> task = executor.submit(r);
          try {
              task.get(timeout, unit);
          } catch (ExecutionException e) {
              throw launderThrowable(e.getCause());
          } finally {
              task.cancel(true);
          }
      }
      
      private static RuntimeException launderThrowable(Throwable t) {
          if (t instanceof RuntimeException) return (RuntimeException)t;
          else if (t instanceof Error) throw (Error)t;
          else throw new IllegalStateException("Not unchecked", t);
      }
      

      (来源:Goetz, Brian, Bloch, Joshua, Bowbeer, Joseph, Lea, Doug, Holmes, David and Peierls, Tim. Java Concurrency in Practice. : Addison-Wesley Longman, Amsterdam, 2006. Listing 5.13 and 7.10

      对于executor,您可以使用Executor.newSingleThreadExecutor() 创建一个新的,或重复使用现有的。

      但请注意:虽然该方法保证在指定超时后返回或抛出异常,但不能保证runnable真的会停止!它会中断正在执行的线程,但如果 runnable 对线程中断没有反应(例如,通过内部检查 Thread.interrupted()),它可能会继续在后台运行 - 可能永远 - 占用一个线程!但至少它不会阻塞。

      解决方案 2:使用自定义线程定时运行: 如果除了线程中断之外还有其他可能取消您的方法调用,您仍然可以使用上述方法,但是您必须使用 @987654327 @ 使用自定义 ThreadFactory 创建一个特殊的 Thread 实例,并覆盖 interrupt 方法:

      Executor executor = Executor.newSingleThreadExecutor(r -> new WsdlThread(r));
      
      public class WsdlThread extends Thread {
          public WsdlThread(Runnable r) { super(r); }
      
          public void interrupt() {
              try {
                  // TODO: do something that will interrupt the wsdl call
                  // e.g. close connection to server, etc.
                  // example: ((WsdlRunnable)r).getWsdlConnection().close();
              } finally {
                  super.interrupt();
              }
          }
      }
      

      如果这也不可能,并且Thread.stop() 也不起作用,那么最后一个解决方案可能会起作用:

      解决方案 3:在另一个 JVM 中启动不可取消的调用:

      使用Runtime.exec 启动另一个JVM 并在那里执行方法调用(有关如何执行此操作的更多信息,请参阅Executing a Java application in a separate process)。 Runtime.exec 会返回一个Process 对象,代表正在运行的进程。

      您可以通过调用destroy()destroyForcibly() 来杀死它。

      【讨论】:

        猜你喜欢
        • 2014-12-19
        • 1970-01-01
        • 2017-05-06
        • 2012-09-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-04
        • 2015-06-10
        相关资源
        最近更新 更多