108

タイムアウトを提供できるExecutorService実装を探しています。ExecutorService に送信されたタスクは、実行にタイムアウトよりも時間がかかる場合、中断されます。このような野獣を実装するのはそれほど難しい作業ではありませんが、既存の実装について知っている人がいるかどうか疑問に思っています。

以下の議論のいくつかに基づいて私が思いついたものは次のとおりです。コメントはありますか?

import java.util.List;
import java.util.concurrent.*;

public class TimeoutThreadPoolExecutor extends ThreadPoolExecutor {
    private final long timeout;
    private final TimeUnit timeoutUnit;

    private final ScheduledExecutorService timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
    private final ConcurrentMap<Runnable, ScheduledFuture> runningTasks = new ConcurrentHashMap<Runnable, ScheduledFuture>();

    public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, long timeout, TimeUnit timeoutUnit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
    }

    public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, long timeout, TimeUnit timeoutUnit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
    }

    public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler, long timeout, TimeUnit timeoutUnit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
    }

    public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler, long timeout, TimeUnit timeoutUnit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
    }

    @Override
    public void shutdown() {
        timeoutExecutor.shutdown();
        super.shutdown();
    }

    @Override
    public List<Runnable> shutdownNow() {
        timeoutExecutor.shutdownNow();
        return super.shutdownNow();
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        if(timeout > 0) {
            final ScheduledFuture<?> scheduled = timeoutExecutor.schedule(new TimeoutTask(t), timeout, timeoutUnit);
            runningTasks.put(r, scheduled);
        }
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        ScheduledFuture timeoutTask = runningTasks.remove(r);
        if(timeoutTask != null) {
            timeoutTask.cancel(false);
        }
    }

    class TimeoutTask implements Runnable {
        private final Thread thread;

        public TimeoutTask(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            thread.interrupt();
        }
    }
}
4

11 に答える 11

94

これにはScheduledExecutorServiceを使用できます。最初に一度だけ送信してすぐに開始し、作成された未来を保持します。その後、一定期間後に保持された未来をキャンセルする新しいタスクを送信できます。

 ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); 
 final Future handler = executor.submit(new Callable(){ ... });
 executor.schedule(new Runnable(){
     public void run(){
         handler.cancel();
     }      
 }, 10000, TimeUnit.MILLISECONDS);

これにより、ハンドラ (中断される主な機能) が 10 秒間実行され、その後、その特定のタスクがキャンセル (中断) されます。

于 2010-05-03T15:12:58.727 に答える
6

タスクを FutureTask でラップすると、FutureTask のタイムアウトを指定できます。この質問に対する私の回答の例を見てください。

Java ネイティブ プロセスのタイムアウト

于 2010-05-03T14:46:55.080 に答える
6

残念ながら、解決策には欠陥があります。には一種のバグがあり、この質問ScheduledThreadPoolExecutorでも報告されています。送信されたタスクをキャンセルしても、タスクに関連付けられているメモリ リソースが完全に解放されません。リソースは、タスクの有効期限が切れたときにのみ解放されます。

したがってTimeoutThreadPoolExecutor、かなり長い有効期限 (一般的な使用方法) で を作成し、タスクを十分に速くサブミットすると、タスクが実際には正常に完了した場合でも、メモリがいっぱいになります。

次の (非常に粗雑な) テスト プログラムで問題を確認できます。

public static void main(String[] args) throws InterruptedException {
    ExecutorService service = new TimeoutThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, 
            new LinkedBlockingQueue<Runnable>(), 10, TimeUnit.MINUTES);
    //ExecutorService service = Executors.newFixedThreadPool(1);
    try {
        final AtomicInteger counter = new AtomicInteger();
        for (long i = 0; i < 10000000; i++) {
            service.submit(new Runnable() {
                @Override
                public void run() {
                    counter.incrementAndGet();
                }
            });
            if (i % 10000 == 0) {
                System.out.println(i + "/" + counter.get());
                while (i > counter.get()) {
                    Thread.sleep(10);
                }
            }
        }
    } finally {
        service.shutdown();
    }
}

プログラムは、生成された が完了するのを待ちますが、使用可能なメモリを使い果たしますRunnable

しばらく考えましたが、残念ながら良い解決策が思いつきませんでした。

編集: この問題はJDK バグ 6602600として報告されており、ごく最近修正されたようです。

于 2012-10-11T16:14:14.073 に答える
1

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.htmlExecutorService.shutDownNow()に記載されている方法を使用してみてはどうでしょうか? それは最も簡単な解決策のようです。

于 2013-03-04T19:27:09.763 に答える
1

John W answer を使用して、タスクが実行を開始したときにタイムアウトを正しく開始する実装を作成しました。単体テストも書いています:)

Future.cancel()ただし、一部の IO 操作は が呼び出されたときに (つまり、が呼び出されたときに) 中断されないため、私のニーズには合いませんThread.interrupt()Thread.interrupt()が呼び出されたときに中断されない可能性のある IO 操作の例としては、 および がSocket.connectありますSocket.read(そして、ほとんどの IO 操作は で実装されていると思いjava.ioます)。のすべての IO 操作は、が呼び出さjava.nioれたときに割り込み可能である必要があります。Thread.interrupt()たとえば、 と がこれに該当しSocketChannel.openますSocketChannel.read

とにかく、誰かが興味を持っている場合は、タスクがタイムアウトできるようにするスレッドプールエグゼキューターの要点を作成しました(割り込み可能な操作を使用している場合...):https://gist.github.com/amanteaux/64c54a913c1ae34ad7b86db109cbc0bf

于 2017-07-16T21:04:42.930 に答える
0

これはどうですか?

final ExecutorService myExecutorService = ...;

// create CompletableFuture to get result/exception from runnable in specified timeout
final CompletableFuture<Object> timeoutFuture = new CompletableFuture<>();

// submit runnable and obtain cancellable Future from executor
final Future<?> cancellableFuture = myExecutorService.submit(() -> {
    try {
        Object result = myMethod(...);
        timeoutFuture.complete(result);
    } catch (Exception e) {
        timeoutFuture.completeExceptionally(e);
    }
});

// block the calling thread until "myMethod" will finish or time out (1 second)
try {
    Object result = timeoutFuture.get(1000, TimeUnit.MILLISECONDS);
    // "myMethod" completed normally
} catch (TimeoutException te) {
    // "myMethod" timed out
    // ...
} catch (ExecutionException ee) {
    // "myMethod" completed exceptionally - get cause
    final Throwable cause = ee.getCause();
    // ...
} catch (InterruptedException ie) {
    // future interrupted
    // ...
} finally {
    // timeoutFuture.cancel(true); // CompletableFuture does not support cancellation
    cancellableFuture.cancel(true); // Future supports cancellation
}
于 2021-03-17T14:41:13.230 に答える