Java:如何停止运行时间过长的线程?

5

假设我有这样一段代码

public void run(){
    Thread behaviourThread = new Thread(abstractBehaviours[i]);
    behaviourThread.start();
}

我希望等待abstractBehaviours[i]运行方法完成或者运行了5000毫秒。我该怎么做?似乎behaviourThread.join(5000)不能做到这一点(我的代码有问题,我把它归结于这个)。
当然,所有的AbstractBehaviour类都是可运行的。我不想在每个run方法里实现它,因为那看起来很丑陋,而且有许多不同的行为,我更愿意在调用/执行线程中执行它,并只执行一次。
解决方案?这是我第一次做这么多线程的事情。谢谢!
编辑:所以打断的解决方案是理想的(需要最少的更改AbstractBehaviour实现)。但是,如果线程已经完成或者已经过了5000毫秒,我需要停止线程,因此像以下的代码可能无法正常工作,因为线程可能会在父线程中的while循环之前结束。有任何解决办法吗?我当然希望能够从启动线程的线程中完成它。
long startTime = System.currentTimeMillis();
behaviourThread.start();
while(!System.currentTimeMilis - startTime < 5000);
behaviourThread.interrupt();
try {
    behaviourThread.join();
} catch (InterruptedException e1) {
    e1.printStackTrace();
}

编辑:算了,我看到有一个Thread.isAlive()方法,我想问题都解决了。


2
@John:这符合他的问题——除了标题,标题谈论的是“停止”线程。 - Michael Borgwardt
4个回答

6
最好的方法是使用线程中断机制。工作线程/Runnable需要定期调用Thread.interrupted()来查看是否到了停止的时间。方程的第二部分是,另一个线程需要在经过5000毫秒后调用Thread.interrupt()来中断工作线程。
使用线程中断的优势(而不是使用标志的定制解决方案)包括:
  • interrupted()状态始终可用于当前线程。您不需要传递对象句柄或使用单例。
  • 中断将取消阻塞一些阻塞IO和同步请求。定制解决方案无法做到这一点。
  • 第三方Java应用程序和库可能会尊重Thread.interrupt()
编辑 - 如评论者所指出,您可以使用Thread.interrupted()Thread.currentThread().isInterrupted()来测试当前线程是否已中断。 两种方法之间的主要区别在于前者会清除中断标志,而后者不会。

我更喜欢使用isInterrupted(),因为它不依赖于中断标志(interruption flag),而像interrupted()方法那样。 - Karussell
@Karussell - 就记录而言,isInterrupted()和interrupted()都“依赖于”中断标志。不同之处在于isInterrupted()不会重置该标志。 - Stephen C
我已经编辑了我的帖子,列出了中断的问题。希望现在更清楚了,或者我可能漏掉了什么?感谢到目前为止回答我的每个人,其中一个解决方案我确定会起作用(但需要更长时间来改变代码)。另一个(中断)可能只是我遗漏了某些东西/思路不够清晰。 - Dr. Thomas C. King
那就是我想表达的意思。因此使用interrupted()有点“危险”。 - Karussell

3
您不能在run方法外部执行此操作 - run方法必须检查某些变量以查看是否应该退出。例如:
class InterruptableRunnable implements Runnable
{
   private volatile boolean stop;
   public setStop() {
       stop = true;
   }

   public void run() {
        while (!stop)
        {
            //do some work and occassionaly fall through to check that stop is still true
        }
   }
}

关键是在运行循环中偶尔检查停止标志。然后,您可以连接一个定时器,在5000毫秒后将stop设置为true。
最好不要直接使用线程,而是使用卓越的并发框架。并发教程是一个很好的开始,书籍Java并发实践也很棒。

你可以通过 System.currentMillis 等方式,在 while 循环语句中包含时间限制。 - Karussell

2
您可以使用java.util.concurrent包来完成此操作。
    ExecutorService service = Executors.newCachedThreadPool();
    Future future = service.submit(behaviourThread);

    long startTime = System.currentTimeMillis();
    while (!future.isDone()) {
        if (System.currentTimeMillis() - startTime > 5000) {
            future.cancel(true);
            break;
        }
    } 
    // TODO: more work here

    //don't forget to shutDown your ThreadPool
    service.shutDown();

如果线程在5秒内没有完成它的工作,下面这段代码将会停止它。如果你检查 behaviourThread.isAlive(),它将显示false


有趣。我看到执行器可能会或可能不会生成另一个线程。 - user9599745

-1

要实现这个功能,您需要实现一个Runnable接口

public void run()
{
  long time = System.nanoTime(),
       end  = time + 5 000 000 000; // just better formatting
  do {
    ...my code

  } while (System.nanoTime() < end && myOwnCondition);
}
  • 中断不是一个好的解决方案,因为你需要从外部访问线程并且它会干扰程序流程。线程可以在你的代码中随时终止,这使得清理工作变得困难。请养成让线程运行到结束的习惯,否则会产生讨厌和困难的错误。

  • 如果你的程序如此繁重,以至于直到任务完成前你都不知道 while 循环何时结束,我建议使用带标签的 break:

    do {
      breakout:
      {
        ..我的代码
        if (timetest)
          break breakout;      
      }
      // 清理工作
      ...
    } while (...);
    

在并发编程中,不响应中断异常是最大的错误。你会注意到许多j.u.c类都会抛出和处理中断异常。在实际编程中,中断异常不仅是一个好的解决方案,通常也是最好的解决方案。如果有人正确地处理中断,那么就不会有你所暗示的那些错误了。 - John Vint

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接