我如何包装一个方法,以便在超过指定的超时时可以终止它的执行?

时间:2008-10-27 15:34:02

标签: java multithreading concurrency callable

我有一个方法,我想打电话。但是,我正在寻找一种干净,简单的方法来杀死它或强迫它返回,如果执行时间太长。

我正在使用Java。

说明:

logger.info("sequentially executing all batches...");
for (TestExecutor executor : builder.getExecutors()) {
logger.info("executing batch...");
executor.execute();
}

我认为TestExecutor类应该implement Callable并继续朝这个方向发展。

但我想要做的就是停止executor.execute(),如果它花了太长时间。

建议...?

修改

收到的许多建议都假设正在执行的方法需要很长时间才能包含某种循环,并且可以定期检查变量。 然而,这种情况并非如此。因此,某些东西不一定是干净的,只会停止执行,这是可以接受的。

7 个答案:

答案 0 :(得分:11)

你应该看看这些类: FutureTaskCallableExecutors

以下是一个例子:

public class TimeoutExample {
    public static Object myMethod() {
        // does your thing and taking a long time to execute
        return someResult;
    }

    public static void main(final String[] args) {
        Callable<Object> callable = new Callable<Object>() {
            public Object call() throws Exception {
                return myMethod();
            }
        };
        ExecutorService executorService = Executors.newCachedThreadPool();

        Future<Object> task = executorService.submit(callable);
        try {
            // ok, wait for 30 seconds max
            Object result = task.get(30, TimeUnit.SECONDS);
            System.out.println("Finished with result: " + result);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        } catch (TimeoutException e) {
            System.out.println("timeout...");
        } catch (InterruptedException e) {
            System.out.println("interrupted");
        }
    }
}

答案 1 :(得分:8)

Java interruption mechanism适用于此类场景。如果您希望中止的方法是执行循环,只需让它在每次迭代时检查线程的interrupted status。如果它被中断,则抛出InterruptedException。

然后,当你想要中止时,你只需要在适当的线程上调用interrupt

或者,您可以使用approach Sun建议作为已弃用停止方法的替代方法。这不涉及抛出任何异常,该方法只会正常返回。

答案 2 :(得分:7)

我假设在以下语句中使用多个线程。

我已经在这个领域做了一些阅读,大多数作者说杀死另一个线程是个坏主意。

如果要杀死的函数可以设计为定期检查变量或同步原语,然后在设置该变量或同步原语时干净地终止,那将非常干净。然后某种监视器线程可以休眠几毫秒,然后设置变量或同步原语。

答案 3 :(得分:3)

真的,你不能......唯一的办法就是使用thread.stop,同意'合作'方法(例如,偶尔检查Thread.isInterrupted或调用抛出InterruptedException的方法,例如Thread.sleep()),或以某种方式完全调用另一个JVM中的方法。

对于某些类型的测试,调用stop()是可以的,但它可能会损坏测试套件的状态,所以如果你想避免交互,你必须在每次调用stop()之后重新启动JVM的效果。

有关如何实施合作方法的详细说明,请查看Sun's FAQ on the deprecated Thread methods

对于现实生活中这种方法的一个例子,Eclipse RCP's Job API's'IProgressMonitor'对象允许一些管理服务通知它们应该停止的子进程(通过'cancel'方法)。当然,这依赖于定期实际检查isCancelled方法的方法,而这些方法通常无法做到。

混合方法可能是通过中断很好地询问线程,然后在几秒钟之后坚持停止。同样,你不应该在生产代码中使用stop,但在这种情况下它可能没问题,尤其是。如果你很快退出JVM。

为了测试这种方法,我编写了一个简单的线束,它使用runnable并尝试执行它。随意评论/编辑。

public void testStop(Runnable r) {
    Thread t = new Thread(r);
    t.start();
    try {
        t.join(2000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    if (!t.isAlive()) {
        System.err.println("Finished on time.");
        return;
    }

    try {
        t.interrupt();
        t.join(2000);
        if (!t.isAlive()) {
            System.err.println("cooperative stop");
            return;
        }
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.err.println("non-cooperative stop");
    StackTraceElement[] trace = Thread.getAllStackTraces().get(t);
    if (null != trace) {
        Throwable temp = new Throwable();
        temp.setStackTrace(trace);
        temp.printStackTrace();
    }
    t.stop();
    System.err.println("stopped non-cooperative thread");
}

为了测试它,我编写了两个竞争的无限循环,一个是协作的,一个永远不会检查其线程的中断位。

public void cooperative() {
    try {
        for (;;) {
            Thread.sleep(500);
        }
    } catch (InterruptedException e) {
        System.err.println("cooperative() interrupted");
    } finally {
        System.err.println("cooperative() finally");
    }
}

public void noncooperative() {
    try {
        for (;;) {
            Thread.yield();
        }
    } finally {
        System.err.println("noncooperative() finally");
    }
}

最后,我编写了测试(JUnit 4)来练习它们:

@Test
public void testStopCooperative() {
    testStop(new Runnable() {
        @Override
        public void run() {
            cooperative();
        }
    });
}

@Test
public void testStopNoncooperative() {
    testStop(new Runnable() {
        @Override
        public void run() {
            noncooperative();
        }
    });
}

之前我从未使用过Thread.stop(),所以我没有意识到它的操作。它的工作原理是从目标线程当前运行的东西中抛出一个ThreadDeath对象。这扩展了错误。因此,虽然它并不总是干净利落,但它通常会留下具有相当合理的程序状态的简单程序。例如,调用任何finally块。如果你想成为一个真正的混蛋,你可以捕捉 ThreadDeath(或错误),并继续运行!

如果没有别的,这真的让我希望更多的代码遵循IProgressMonitor方法 - 为可能花费一段时间的方法添加另一个参数,并鼓励该方法的实现者偶尔轮询Monitor对象看用户是否希望系统放弃。我将来会尝试遵循这种模式,特别是可能是交互式的方法。当然,你不一定事先知道将采用哪种方法,但我认为这就是Profilers的用法。

至于'完全启动另一个JVM'方法,这将需要更多的工作。我不知道是否有人编写了委托类加载器,或者是否有一个包含在JVM中,但是这种方法需要这样做。

答案 4 :(得分:1)

没有人直接回答,所以这是我能用短暂的伪代码给你的最接近的东西:

将该方法包装在runnable / callable中。如果你希望它停止,方法本身就必须检查中断状态(例如,如果这个方法是一个循环,在循环中检查Thread.currentThread()。isInterrupted,如果是,则停止循环(don不过要检查每次迭代,否则你只会放慢速度。 在wrap方法中,使用thread.join(timeout)等待你想让方法运行的时间。或者,在循环内部,如果您在等待时需要做其他事情,则使用较小的超时重复调用连接。如果方法没有完成,在加入后,使用上述建议中止快速/清理。

代码明智,旧代码:

void myMethod()
{
    methodTakingAllTheTime();
}

新代码:

void myMethod()
{
    Thread t = new Thread(new Runnable()
        {
            public void run()
            {
                 methodTakingAllTheTime(); // modify the internals of this method to check for interruption
            }
        });
    t.join(5000); // 5 seconds
    t.interrupt();
}

但是,为了使其运行良好,您仍然需要修改methodTakingAllTheTime,否则该线程将在您调用中断后继续运行。

答案 5 :(得分:0)

我认为,正确的答案是创建一个Runnable来执行子程序,并在一个单独的Thread中运行它。 Runnable可能是FutureTask,你可以使用超时(“get”方法)运行。如果它超时,你会得到一个TimeoutException,我建议你

  • 调用thread.interrupt()尝试以半合作的方式结束它(许多库调用似乎对此很敏感,所以它可能会起作用)
  • 稍等一下(Thread.sleep(300))
  • 然后,如果线程仍处于活动状态(thread.isActive()),则调用thread.stop()。这是一种不赞成使用的方法,但显然是城里唯一的游戏,而不是运行一个单独的过程,所有这一切都需要。

在我的应用程序中,我运行由初学者编写的不可靠,不合作的代码,我执行上述操作,确保被杀死的线程永远不会(写)访问任何在其死亡后幸存的对象。这包括容纳被调用方法的对象,如果发生超时则将其丢弃。 (我告诉我的学生避免超时,因为他们的经纪人将被取消资格。)我不确定内存泄漏......

我区分长运行时(方法终止)和硬超时 - 硬超时更长,并且意味着在代码根本不终止时捕获这种情况,而不是缓慢。

根据我的研究,Java似乎没有一个非弃用的运行非合作代码的规定,这在某种程度上是安全模型中的一个漏洞。要么我可以运行外部代码并控制它拥有的权限(SecurityManager),要么我无法运行外部代码,因为它可能最终占用整个CPU而没有不弃用的方法来阻止它。

double x = 2.0;  
while(true) {x = x*x}; // do not terminate
System.out.print(x); // prevent optimization

答案 6 :(得分:0)

我可以想到一个不太好的方法来做到这一点。如果您可以检测何时花费太多时间,则可以让方法在每个步骤中检查布尔值。如果程序占用太多时间,程序会将boolean tooMuchTime的值更改为true(我无法帮助解决此问题)。然后使用这样的东西:

 Method(){
 //task1
if (tooMuchTime == true) return;
 //task2
if (tooMuchTime == true) return;
 //task3
if (tooMuchTime == true) return;
//task4
if (tooMuchTime == true) return;
//task5
if (tooMuchTime == true) return;
//final task
  }