ExecutorService, который прерывает задачи после тайм-аута - PullRequest
83 голосов
/ 03 мая 2010

Я ищу реализацию 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();
        }
    }
}

Ответы [ 8 ]

79 голосов
/ 03 мая 2010

Для этого вы можете использовать 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 секунд, а затем отменит (то есть прервет) эту конкретную задачу.

5 голосов
/ 11 октября 2012

К сожалению, решение имеет недостатки. Существует ошибка типа 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 s завершатся.

Я немного об этом подумал, но, к сожалению, не смог придумать хорошее решение.

EDIT: Я обнаружил, что эта проблема была зарегистрирована как ошибка JDK 6602600 , и, похоже, она была исправлена ​​совсем недавно.

4 голосов
/ 03 мая 2010

Оберните задачу в FutureTask, и вы можете указать время ожидания для FutureTask. Посмотрите на пример в моем ответе на этот вопрос,

Тайм-аут Java-процесса

1 голос
/ 17 июля 2017

Используя ответ John W, я создал реализацию, которая правильно начинает тайм-аут, когда задача начинает свое выполнение. Я даже для него пишу юнит-тест:)

Однако это не удовлетворяет моим потребностям, поскольку некоторые операции ввода-вывода не прерываются при вызове Future.cancel() (т.е. при вызове Thread.interrupted()). Вот некоторые примеры операций ввода-вывода, которые могут не прерываться при вызове Thread.interrupted(): Socket.connect и Socket.read (и я подозреваю, что большая часть операций ввода-вывода реализована в java.io). Все операции ввода-вывода в java.nio должны прерываться при вызове Thread.interrupted(). Например, это относится к SocketChannel.open и SocketChannel.read.

В любом случае, если кому-то интересно, я создал суть для исполнителя пула потоков, который позволяет тайм-аутам задач (если они используют прерываемые операции ...): https://gist.github.com/amanteaux/64c54a913c1ae34ad7b86db109cbc0bf

1 голос
/ 02 августа 2016

Через тонну времени для обследования,
Наконец, я использую invokeAll метод ExecutorService для решения этой проблемы.
Это будет строго прерывать задачу во время ее выполнения.
Вот пример

ExecutorService executorService = Executors.newCachedThreadPool();

try {
    List<Callable<Object>> callables = new ArrayList<>();
    // Add your long time task (callable)
    callables.add(new VaryLongTimeTask());
    // Assign tasks for specific execution timeout (e.g. 2 sec)
    List<Future<Object>> futures = executorService.invokeAll(callables, 2000, TimeUnit.MILLISECONDS);
    for (Future<Object> future : futures) {
        // Getting result
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

executorService.shutdown();

Плюс вы также можете отправить ListenableFuture на тот же ExecutorService.
Просто немного измените первую строку кода.

ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

ListeningExecutorService - это функция прослушивания ExecutorService в проекте Google Guava ( com.google.guava ))

1 голос
/ 27 декабря 2013

Кажется, проблема не в ошибке JDK 6602600 (она была решена в 2010-05-22), а в неправильный вызов сна (10) в круге. Кроме того, обратите внимание, что основной поток должен дать напрямую ШАНС для других задач, чтобы реализовать свои задачи, вызывая SLEEP (0) в КАЖДАЯ ветвь внешнего круга. Я думаю, что лучше использовать Thread.yield () вместо Thread.sleep (0)

Исправленная в результате часть предыдущего кода проблемы выглядит следующим образом:

.......................
........................
Thread.yield();         

if (i % 1000== 0) {
System.out.println(i + "/" + counter.get()+ "/"+service.toString());
}

//                
//                while (i > counter.get()) {
//                    Thread.sleep(10);
//                } 

Работает корректно с количеством внешних счетчиков до 150 000 000 проверенных кругов.

1 голос
/ 04 марта 2013

Как насчет использования метода ExecutorService.shutDownNow(), как описано в http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html? Кажется, это самое простое решение.

0 голосов
/ 07 августа 2015

А как насчет этой альтернативной идеи:

  • у двух есть два исполнителя:
    • один для:
      • отправка задания, не заботясь о тайм-ауте задания
      • добавление будущего и времени, когда оно должно закончиться внутренней структурой
    • один для выполнения внутренней работы, которая проверяет внутреннюю структуру, если некоторые задачи имеют тайм-аут и если их нужно отменить

Небольшой образец здесь:

public class AlternativeExecutorService 
{

private final CopyOnWriteArrayList<ListenableFutureTask> futureQueue       = new CopyOnWriteArrayList();
private final ScheduledThreadPoolExecutor                scheduledExecutor = new ScheduledThreadPoolExecutor(1); // used for internal cleaning job
private final ListeningExecutorService                   threadExecutor    = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5)); // used for
private ScheduledFuture scheduledFuture;
private static final long INTERNAL_JOB_CLEANUP_FREQUENCY = 1000L;

public AlternativeExecutorService()
{
    scheduledFuture = scheduledExecutor.scheduleAtFixedRate(new TimeoutManagerJob(), 0, INTERNAL_JOB_CLEANUP_FREQUENCY, TimeUnit.MILLISECONDS);
}

public void pushTask(OwnTask task)
{
    ListenableFuture<Void> future = threadExecutor.submit(task);  // -> create your Callable
    futureQueue.add(new ListenableFutureTask(future, task, getCurrentMillisecondsTime())); // -> store the time when the task should end
}

public void shutdownInternalScheduledExecutor()
{
    scheduledFuture.cancel(true);
    scheduledExecutor.shutdownNow();
}

long getCurrentMillisecondsTime()
{
    return Calendar.getInstance().get(Calendar.MILLISECOND);
}

class ListenableFutureTask
{
    private final ListenableFuture<Void> future;
    private final OwnTask                task;
    private final long                   milliSecEndTime;

    private ListenableFutureTask(ListenableFuture<Void> future, OwnTask task, long milliSecStartTime)
    {
        this.future = future;
        this.task = task;
        this.milliSecEndTime = milliSecStartTime + task.getTimeUnit().convert(task.getTimeoutDuration(), TimeUnit.MILLISECONDS);
    }

    ListenableFuture<Void> getFuture()
    {
        return future;
    }

    OwnTask getTask()
    {
        return task;
    }

    long getMilliSecEndTime()
    {
        return milliSecEndTime;
    }
}

class TimeoutManagerJob implements Runnable
{
    CopyOnWriteArrayList<ListenableFutureTask> getCopyOnWriteArrayList()
    {
        return futureQueue;
    }

    @Override
    public void run()
    {
        long currentMileSecValue = getCurrentMillisecondsTime();
        for (ListenableFutureTask futureTask : futureQueue)
        {
            consumeFuture(futureTask, currentMileSecValue);
        }
    }

    private void consumeFuture(ListenableFutureTask futureTask, long currentMileSecValue)
    {
        ListenableFuture<Void> future = futureTask.getFuture();
        boolean isTimeout = futureTask.getMilliSecEndTime() >= currentMileSecValue;
        if (isTimeout)
        {
            if (!future.isDone())
            {
                future.cancel(true);
            }
            futureQueue.remove(futureTask);
        }
    }
}

class OwnTask implements Callable<Void>
{
    private long     timeoutDuration;
    private TimeUnit timeUnit;

    OwnTask(long timeoutDuration, TimeUnit timeUnit)
    {
        this.timeoutDuration = timeoutDuration;
        this.timeUnit = timeUnit;
    }

    @Override
    public Void call() throws Exception
    {
        // do logic
        return null;
    }

    public long getTimeoutDuration()
    {
        return timeoutDuration;
    }

    public TimeUnit getTimeUnit()
    {
        return timeUnit;
    }
}
}
...