Java 如何使线程超时

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2275443/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 05:34:44  来源:igfitidea点击:

How to timeout a thread

javatimeoutmultithreadingtimer

提问by java_geek

I want to run a thread for some fixed amount of time. If it is not completed within that time, I want to either kill it, throw some exception, or handle it in some way. How can it be done?

我想在一段固定的时间内运行一个线程。如果在那段时间内没有完成,我想杀死它,抛出一些异常,或者以某种方式处理它。怎么做到呢?

One way of doing it as I figured out from this threadis to use a TimerTask inside the run() method of the Thread.

正如我从这个线程中发现的那样,一种方法是在线程的 run() 方法中使用 TimerTask。

Are there any better solutions for this?

有没有更好的解决方案?

 
EDIT: Adding a bounty as I needed a clearer answer. The ExecutorService code given below does not address my problem. Why should I sleep() after executing (some code - I have no handle over this piece of code)? If the code is completed and the sleep() is interrupted, how can that be a timeOut?

 
编辑:添加赏金,因为我需要一个更清晰的答案。下面给出的 ExecutorService 代码没有解决我的问题。为什么我应该在执行后 sleep() (一些代码 - 我无法处理这段代码)?如果代码完成并且 sleep() 被中断,那怎么可能是超时?

The task that needs to be executed is not in my control. It can be any piece of code. The problem is this piece of code might run into an infinite loop. I don't want that to happen. So, I just want to run that task in a separate thread. The parent thread has to wait till that thread finishes and needs to know the status of the task (i.e whether it timed out or some exception occured or if its a success). If the task goes into an infinite loop, my parent thread keeps on waiting indefinitely, which is not an ideal situation.

需要执行的任务不在我的控制范围内。它可以是任何一段代码。问题是这段代码可能会陷入无限循环。我不希望这种情况发生。所以,我只想在单独的线程中运行该任务。父线程必须等到该线程完成并需要知道任务的状态(即是否超时或发生了某些异常或是否成功)。如果任务进入无限循环,我的父线程会无限期地等待,这不是理想的情况。

采纳答案by BalusC

Indeed rather use ExecutorServiceinstead of Timer, here's an SSCCE:

确实,而是使用ExecutorService而不是Timer,这是一个SSCCE

package com.stackoverflow.q2275443;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Test {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new Task());

        try {
            System.out.println("Started..");
            System.out.println(future.get(3, TimeUnit.SECONDS));
            System.out.println("Finished!");
        } catch (TimeoutException e) {
            future.cancel(true);
            System.out.println("Terminated!");
        }

        executor.shutdownNow();
    }
}

class Task implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.sleep(4000); // Just to demo a long running task of 4 seconds.
        return "Ready!";
    }
}

Play a bit with the timeoutargument in Future#get()method, e.g. increase it to 5 and you'll see that the thread finishes. You can intercept the timeout in the catch (TimeoutException e)block.

稍微使用方法中的timeout参数Future#get(),例如将其增加到 5,您将看到线程完成。您可以在catch (TimeoutException e)块中拦截超时。

Update:to clarify a conceptual misunderstanding, the sleep()is notrequired. It is just used for SSCCE/demonstration purposes. Just do yourlong running task right there in place of sleep(). Inside your long running task, you should be checking if the thread is not interruptedas follows:

更新:澄清一个概念的误解,将sleep()必需的。它仅用于 SSCCE/演示目的。只要做你的长时间运行的任务就在那里发生的sleep()。在长时间运行的任务中,您应该检查线程是否没有中断,如下所示:

while (!Thread.interrupted()) {
    // Do your long running task here.
}

回答by Drew Wills

Consider using an instance of ExecutorService. Both invokeAll()and invokeAny()methods are available with a timeoutparameter.

考虑使用ExecutorService的实例。这两个invokeAll()invokeAny()方法都可以用timeout参数。

The current thread will block until the method completes (not sure if this is desirable) either because the task(s) completed normally or the timeout was reached. You can inspect the returned Future(s) to determine what happened.

当前线程将阻塞,直到方法完成(不确定这是否可取),因为任务正常完成或达到超时。您可以检查返回的Future(s) 以确定发生了什么。

回答by erickson

There isn't a 100% reliable way to do this for any old task. The task has to be written with this ability in mind.

对于任何旧任务,都没有 100% 可靠的方法来执行此操作。编写任务时必须考虑到这种能力。

Core Java libraries like ExecutorServicecancel asynchronous tasks with interrupt()calls on the worker thread. So, for example, if the task contains some sort of loop, you should be checking its interrupt statuson each iteration. If the task is doing I/O operations, they should be interruptible too—and setting that up can be tricky. In any case, keep in mind that code has to actively check for interrupts; setting an interrupt doesn't necessarily do anything.

核心 Java 库,例如ExecutorService通过interrupt()调用工作线程取消异步任务。因此,例如,如果任务包含某种循环,您应该在每次迭代时检查其中断状态。如果任务正在执行 I/O 操作,它们也应该是可中断的——而设置它可能会很棘手。无论如何,请记住代码必须主动检查中断;设置中断不一定做任何事情。

Of course, if your task is some simple loop, you can just check the current time at each iteration and give up when a specified timeout has elapsed. A worker thread isn't needed in that case.

当然,如果你的任务是一些简单的循环,你可以在每次迭代时检查当前时间,并在指定的超时时间过后放弃。在这种情况下不需要工作线程。

回答by sfussenegger

I think the answer mainly depends on the task itself.

我认为答案主要取决于任务本身。

  • Is it doing one task over and over again?
  • Is it necessary that the timeout interrupts a currently running task immediately after it expires?
  • 它是一遍又一遍地做一项任务吗?
  • 超时是否有必要在超时后立即中断当前正在运行的任务?

If the first answer is yes and the second is no, you could keep it as simple as this:

如果第一个答案是肯定的,第二个答案是否定的,您可以保持简单:

public class Main {

    private static final class TimeoutTask extends Thread {
        private final long _timeoutMs;
        private Runnable _runnable;

        private TimeoutTask(long timeoutMs, Runnable runnable) {
            _timeoutMs = timeoutMs;
            _runnable = runnable;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() < (start + _timeoutMs)) {
                _runnable.run();
            }
            System.out.println("execution took " + (System.currentTimeMillis() - start) +" ms");
        }

    }

    public static void main(String[] args) throws Exception {
        new TimeoutTask(2000L, new Runnable() {

            @Override
            public void run() {
                System.out.println("doing something ...");
                try {
                    // pretend it's taking somewhat longer than it really does
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }
}

If this isn't an option, please narrow your requirements - or show some code.

如果这不是一个选项,请缩小您的要求 - 或显示一些代码。

回答by Dieter

I think you should take a look at proper concurrency handling mechanisms (threads running into infinite loops doesn't sound good per se, btw). Make sure you read a little about the "killing" or "stopping" Threadstopic.

我认为你应该看看适当的并发处理机制(线程进入无限循环本身听起来并不好,顺便说一句)。确保您阅读了一些有关“杀死”或“停止”线程主题的内容。

What you are describing,sound very much like a "rendezvous", so you may want to take a look at the CyclicBarrier.

你所描述的,听起来很像一个“集合点”,所以你可能想看看CyclicBarrier

There may be other constructs (like using CountDownLatchfor example) that can resolve your problem (one thread waiting with a timeout for the latch, the other should count down the latch if it has done it's work, which would release your first thread either after a timeout or when the latch countdown is invoked).

可能还有其他构造(例如使用CountDownLatch)可以解决您的问题(一个线程等待锁存器超时,另一个线程应该倒计时锁存器,如果它已经完成它的工作,这将释放您的第一个线程之后超时或调用闩锁倒计时时)。

I usually recommend two books in this area: Concurrent Programming in Javaand Java Concurrency in Practice.

我通常推荐这方面的两本书:Java并发编程Java并发实践

回答by elou

I post you a piece of code which show a way how to solve the problem. As exemple I'm reading a file. You could use this method for another operation, but you need to implements the kill() method so that the main operation will be interrupted.

我给你贴了一段代码,它展示了如何解决这个问题的方法。例如,我正在阅读一个文件。您可以将此方法用于其他操作,但您需要实现 kill() 方法,以便中断主操作。

hope it helps

希望能帮助到你


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * Main class
 * 
 * @author el
 * 
 */
public class Main {
    /**
     * Thread which perform the task which should be timed out.
     * 
     * @author el
     * 
     */
    public static class MainThread extends Thread {
        /**
         * For example reading a file. File to read.
         */
        final private File fileToRead;
        /**
         * InputStream from the file.
         */
        final private InputStream myInputStream;
        /**
         * Thread for timeout.
         */
        final private TimeOutThread timeOutThread;

        /**
         * true if the thread has not ended.
         */
        boolean isRunning = true;

        /**
         * true if all tasks where done.
         */
        boolean everythingDone = false;

        /**
         * if every thing could not be done, an {@link Exception} may have
         * Happens.
         */
        Throwable endedWithException = null;

        /**
         * Constructor.
         * 
         * @param file
         * @throws FileNotFoundException
         */
        MainThread(File file) throws FileNotFoundException {
            setDaemon(false);
            fileToRead = file;
            // open the file stream.
            myInputStream = new FileInputStream(fileToRead);
            // Instantiate the timeout thread.
            timeOutThread = new TimeOutThread(10000, this);
        }

        /**
         * Used by the {@link TimeOutThread}.
         */
        public void kill() {
            if (isRunning) {
                isRunning = false;
                if (myInputStream != null) {
                    try {
                        // close the stream, it may be the problem.
                        myInputStream.close();
                    } catch (IOException e) {
                        // Not interesting
                        System.out.println(e.toString());
                    }
                }
                synchronized (this) {
                    notify();
                }
            }
        }

        /**
         * The task which should be timed out.
         */
        @Override
        public void run() {
            timeOutThread.start();
            int bytes = 0;
            try {
                // do something
                while (myInputStream.read() >= 0) {
                    // may block the thread.
                    myInputStream.read();
                    bytes++;
                    // simulate a slow stream.
                    synchronized (this) {
                        wait(10);
                    }
                }
                everythingDone = true;
            } catch (IOException e) {
                endedWithException = e;
            } catch (InterruptedException e) {
                endedWithException = e;
            } finally {
                timeOutThread.kill();
                System.out.println("-->read " + bytes + " bytes.");
                isRunning = false;
                synchronized (this) {
                    notifyAll();
                }
            }
        }
    }

    /**
     * Timeout Thread. Kill the main task if necessary.
     * 
     * @author el
     * 
     */
    public static class TimeOutThread extends Thread {
        final long timeout;
        final MainThread controlledObj;

        TimeOutThread(long timeout, MainThread controlledObj) {
            setDaemon(true);
            this.timeout = timeout;
            this.controlledObj = controlledObj;
        }

        boolean isRunning = true;

        /**
         * If we done need the {@link TimeOutThread} thread, we may kill it.
         */
        public void kill() {
            isRunning = false;
            synchronized (this) {
                notify();
            }
        }

        /**
         * 
         */
        @Override
        public void run() {
            long deltaT = 0l;
            try {
                long start = System.currentTimeMillis();
                while (isRunning && deltaT < timeout) {
                    synchronized (this) {
                        wait(Math.max(100, timeout - deltaT));
                    }
                    deltaT = System.currentTimeMillis() - start;
                }
            } catch (InterruptedException e) {
                // If the thread is interrupted,
                // you may not want to kill the main thread,
                // but probably yes.
            } finally {
                isRunning = false;
            }
            controlledObj.kill();
        }
    }

    /**
     * Start the main task and wait for the end.
     * 
     * @param args
     * @throws FileNotFoundException
     */
    public static void main(String[] args) throws FileNotFoundException {
        long start = System.currentTimeMillis();
        MainThread main = new MainThread(new File(args[0]));
        main.start();
        try {
            while (main.isRunning) {
                synchronized (main) {
                    main.wait(1000);
                }
            }
            long stop = System.currentTimeMillis();

            if (main.everythingDone)
                System.out.println("all done in " + (stop - start) + " ms.");
            else {
                System.out.println("could not do everything in "
                        + (stop - start) + " ms.");
                if (main.endedWithException != null)
                    main.endedWithException.printStackTrace();
            }
        } catch (InterruptedException e) {
            System.out.println("You've killed me!");
        }
    }
}

Regards

问候

回答by Dan Puzey

One thing that I've not seen mentioned is that killing threads is generally a Bad Idea. There are techniques for making threaded methods cleanly abortable, but that's different to just killing a thread after a timeout.

我没有提到的一件事是杀死线程通常是一个坏主意。有一些技术可以使线程方法完全中止,但这与在超时后终止线程不同。

The risk with what you're suggesting is that you probably don't know what state the thread will be in when you kill it - so you risk introducing instability. A better solution is to make sure your threaded code either doesn't hang itself, or will respond nicely to an abort request.

您所建议的风险在于,您可能不知道杀死线程时线程将处于什么状态 - 因此您可能会引入不稳定。更好的解决方案是确保您的线程代码不会自行挂起,或者会很好地响应中止请求。

回答by markusk

The following snippet will start an operation in a separate thread, then wait for up to 10 seconds for the operation to complete. If the operation does not complete in time, the code will attempt to cancel the operation, then continue on its merry way. Even if the operation cannot be cancelled easily, the parent thread will not wait for the child thread to terminate.

以下代码段将在单独的线程中启动操作,然后最多等待 10 秒以完成操作。如果操作没有及时完成,代码将尝试取消操作,然后继续其愉快的方式。即使操作不能轻易取消,父线程也不会等待子线程终止。

ExecutorService executorService = getExecutorService();
Future<SomeClass> future = executorService.submit(new Callable<SomeClass>() {
    public SomeClass call() {
        // Perform long-running task, return result. The code should check
        // interrupt status regularly, to facilitate cancellation.
    }
});
try {
    // Real life code should define the timeout as a constant or
    // retrieve it from configuration
    SomeClass result = future.get(10, TimeUnit.SECONDS);
    // Do something with the result
} catch (TimeoutException e) {
    future.cancel(true);
    // Perform other error handling, e.g. logging, throwing an exception
}

The getExecutorService()method can be implemented in a number of ways. If you do not have any particular requirements, you can simply call Executors.newCachedThreadPool()for thread pooling with no upper limit on the number of threads.

getExecutorService()方法可以以多种方式实施。如果您没有任何特殊要求,您可以简单地调用Executors.newCachedThreadPool()没有线程数上限的线程池。

回答by Peter Tseng

Assuming the thread code is out of your control:

假设线程代码不受您的控制:

From the Java documentationmentioned above:

从上面提到的Java文档

What if a thread doesn't respond to Thread.interrupt?

In some cases, you can use application specific tricks. For example, if a thread is waiting on a known socket, you can close the socket to cause the thread to return immediately. Unfortunately, there really isn't any technique that works in general. It should be noted that in all situations where a waiting thread doesn't respond to Thread.interrupt, it wouldn't respond to Thread.stop either.Such cases include deliberate denial-of-service attacks, and I/O operations for which thread.stop and thread.interrupt do not work properly.

如果线程不响应 Thread.interrupt 怎么办?

在某些情况下,您可以使用特定于应用程序的技巧。例如,如果一个线程正在等待一个已知的套接字,您可以关闭该套接字使该线程立即返回。不幸的是,真的没有任何技术可以通用。应该注意的是,在等待线程不响应 Thread.interrupt 的所有情况下,它也不会响应 Thread.stop。此类情况包括故意拒绝服务攻击,以及 thread.stop 和 thread.interrupt 无法正常工作的 I/O 操作。

Bottom Line:

底线:

Make sure all threads can be interrupted, or else you need specific knowledge of the thread - like having a flag to set. Maybe you can require that the task be given to you along with the code needed to stop it - define an interface with a stop()method. You can also warn when you failed to stop a task.

确保所有线程都可以被中断,否则您需要特定的线程知识——比如设置一个标志。也许您可以要求将任务与停止它所需的代码一起提供给您 - 定义一个带有stop()方法的接口。您还可以在停止任务失败时发出警告。

回答by user1310503

BalusC said:

BalusC 说:

Update: to clarify a conceptual misunderstanding, the sleep() is not required. It is just used for SSCCE/demonstration purposes. Just do your long running task right there in place of sleep().

更新:为了澄清概念上的误解,不需要 sleep()。它仅用于 SSCCE/演示目的。只需在那里代替 sleep() 执行长时间运行的任务即可。

But if you replace Thread.sleep(4000);with for (int i = 0; i < 5E8; i++) {}then it doesn't compile, because the empty loop doesn't throw an InterruptedException.

但是如果你用Thread.sleep(4000);with替换for (int i = 0; i < 5E8; i++) {}它就不会编译,因为空循环不会抛出InterruptedException.

And for the thread to be interruptible, it needs to throw an InterruptedException.

为了使线程可中断,它需要抛出一个InterruptedException.

This seems like a serious problem to me. I can't see how to adapt this answer to work with a general long-running task.

这对我来说似乎是一个严重的问题。我不知道如何调整这个答案以处理一般的长期运行任务。

Edited to add: I reasked this as a new question: [ interrupting a thread after fixed time, does it have to throw InterruptedException?]

编辑添加:我重新提出了一个新问题:[在固定时间后中断线程,是否必须抛出InterruptedException?]