Как вызвать какой-нибудь метод блокировки с таймаутом в Java?

97

Есть ли стандартный хороший способ вызвать метод блокировки с тайм-аутом в Java? Я хочу уметь:

// call something.blockingMethod();
// if it hasn't come back within 2 seconds, forget it

если это имеет смысл.

Спасибо.

Jjujuma
источник
1
В качестве справки ознакомьтесь с «Java Concurrency in Practice» Брайана Гетца, стр. 126–134, в частности, в разделе 6.3.7 «Установка временных ограничений на задачи»
коричневый. 2179,

Ответы:

152

Вы можете использовать Executor:

ExecutorService executor = Executors.newCachedThreadPool();
Callable<Object> task = new Callable<Object>() {
   public Object call() {
      return something.blockingMethod();
   }
};
Future<Object> future = executor.submit(task);
try {
   Object result = future.get(5, TimeUnit.SECONDS); 
} catch (TimeoutException ex) {
   // handle the timeout
} catch (InterruptedException e) {
   // handle the interrupts
} catch (ExecutionException e) {
   // handle other exceptions
} finally {
   future.cancel(true); // may or may not desire this
}

Если future.getне возвращается в течение 5 секунд, он бросает TimeoutException. Тайм-аут может быть настроен в секундах, минутах, миллисекундах или в любых единицах, доступных в качестве константы в TimeUnit.

См. JavaDoc для более подробной информации.

Скаффман
источник
13
Метод блокировки будет продолжать работать даже после тайм-аута, верно?
Иван Дубров
1
Это зависит от future.cancel. В зависимости от того, что делает блокирующий метод в данный момент, он может прекратиться, а может и нет.
skaffman
4
как передать параметр в blockingMethod ()? Спасибо!
Роберт Хенру
@RobertAHenru: создайте новый класс с именем, конструктор BlockingMethodCallableкоторого принимает параметры, которые вы хотите передать, blockingMethod()и сохраните их как переменные-члены (возможно, как окончательные). Затем внутри call()передайте эти параметры в blockMethod().
Vite Falcon
1
наконец-то должен сделать future.cancel(true)- Метод cancel (логический) в типе Future <Object> не применим для arguments ()
Ноам Манос
9

Вы можете заключить вызов в a FutureTaskи использовать версию get () с тайм-аутом.

См. Http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/FutureTask.html

Колин Гуди
источник
1
FutureTask не является асинхронным, не так ли? Сам по себе он просто выполняет все синхронно, вам нужно объединить его с Executor, чтобы нарушить асинхронное поведение.
skaffman,
6

Смотрите также гуавы в TimeLimiter , которая использует Исполнитель за кулисами.

Федерико
источник
1
Отличная идея. Но ссылка устарела. Googlecode больше нет .. попробуйте эту ссылку на SimpleTimeLimiter
Rhubarb
4

Это действительно здорово, что люди пытаются реализовать это разными способами. Но правда в том, что пути нет.

Большинство разработчиков попытаются поместить блокирующий вызов в другой поток и иметь будущее или какой-то таймер. НО в Java нет способа остановить поток извне, не говоря уже о некоторых очень конкретных случаях, таких как методы Thread.sleep () и Lock.lockInterruptibly (), которые явно обрабатывают прерывание потока.

Итак, на самом деле у вас есть только 3 общих варианта:

  1. Поместите свой блокирующий вызов в новый поток, и если время истечет, вы просто продолжите, оставив этот поток висеть. В этом случае вы должны убедиться, что поток настроен как поток демона. Таким образом, поток не остановит ваше приложение от завершения.

  2. Используйте неблокирующие API Java. Так, например, для сети используйте NIO2 и используйте неблокирующие методы. Для чтения из консоли используйте Scanner.hasNext () перед блокировкой и т. Д.

  3. Если ваш блокирующий вызов - это не ввод-вывод, а ваша логика, то вы можете повторно проверить, не Thread.isInterrupted()был ли он прерван извне, и вызвать другой поток thread.interrupt()в блокирующем потоке.

Этот курс о параллелизме https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

действительно проходит через эти основы, если вы действительно хотите понять, как это работает в Java. На самом деле он говорит об этих конкретных ограничениях и сценариях, а также о том, как их обойти в одной из лекций.

Лично я стараюсь программировать, по возможности не используя блокирующие вызовы. Например, существуют наборы инструментов, такие как Vert.x, которые позволяют легко и эффективно выполнять операции ввода-вывода и без операций ввода-вывода асинхронно и неблокирующим образом.

Я надеюсь, что это помогает

Майкл П
источник
3

Для этого также существует решение AspectJ с библиотекой jcabi-sizes .

@Timeable(limit = 30, unit = TimeUnit.MINUTES)
public Soup cookSoup() {
  // Cook soup, but for no more than 30 minutes (throw and exception if it takes any longer
}

Он не может быть более лаконичным, но вы, конечно, должны полагаться на AspectJ и вводить его в жизненный цикл сборки.

Есть статья, объясняющая это дальше: Ограничение времени выполнения метода Java

гвласов
источник
1
Thread thread = new Thread(new Runnable() {
    public void run() {
        something.blockingMethod();
    }
});
thread.start();
thread.join(2000);
if (thread.isAlive()) {
    thread.stop();
}

Обратите внимание, что остановка устарела, лучшая альтернатива - установить какой-нибудь изменчивый логический флаг, внутри blockingMethod () проверьте его и выйдите, например:

import org.junit.*;
import java.util.*;
import junit.framework.TestCase;

public class ThreadTest extends TestCase {
    static class Something implements Runnable {
        private volatile boolean stopRequested;
        private final int steps;
        private final long waitPerStep;

        public Something(int steps, long waitPerStep) {
            this.steps = steps;
            this.waitPerStep = waitPerStep;
        }

        @Override
        public void run() {
            blockingMethod();
        }

        public void blockingMethod() {
            try {
                for (int i = 0; i < steps && !stopRequested; i++) {
                    doALittleBit();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        public void doALittleBit() throws InterruptedException {
            Thread.sleep(waitPerStep);
        }

        public void setStopRequested(boolean stopRequested) {
            this.stopRequested = stopRequested;
        }
    }

    @Test
    public void test() throws InterruptedException {
        final Something somethingRunnable = new Something(5, 1000);
        Thread thread = new Thread(somethingRunnable);
        thread.start();
        thread.join(2000);
        if (thread.isAlive()) {
            somethingRunnable.setStopRequested(true);
            thread.join(2000);
            assertFalse(thread.isAlive());
        } else {
            fail("Exptected to be alive (5 * 1000 > 2000)");
        }
    }
}
младший
источник
1

Попробуй это. Более простое решение. Гарантирует, что если блок не будет выполнен в срок. процесс завершится и выдаст исключение.

public class TimeoutBlock {

 private final long timeoutMilliSeconds;
    private long timeoutInteval=100;

    public TimeoutBlock(long timeoutMilliSeconds){
        this.timeoutMilliSeconds=timeoutMilliSeconds;
    }

    public void addBlock(Runnable runnable) throws Throwable{
        long collectIntervals=0;
        Thread timeoutWorker=new Thread(runnable);
        timeoutWorker.start();
        do{ 
            if(collectIntervals>=this.timeoutMilliSeconds){
                timeoutWorker.stop();
                throw new Exception("<<<<<<<<<<****>>>>>>>>>>> Timeout Block Execution Time Exceeded In "+timeoutMilliSeconds+" Milli Seconds. Thread Block Terminated.");
            }
            collectIntervals+=timeoutInteval;           
            Thread.sleep(timeoutInteval);

        }while(timeoutWorker.isAlive());
        System.out.println("<<<<<<<<<<####>>>>>>>>>>> Timeout Block Executed Within "+collectIntervals+" Milli Seconds.");
    }

    /**
     * @return the timeoutInteval
     */
    public long getTimeoutInteval() {
        return timeoutInteval;
    }

    /**
     * @param timeoutInteval the timeoutInteval to set
     */
    public void setTimeoutInteval(long timeoutInteval) {
        this.timeoutInteval = timeoutInteval;
    }
}

пример :

try {
        TimeoutBlock timeoutBlock = new TimeoutBlock(10 * 60 * 1000);//set timeout in milliseconds
        Runnable block=new Runnable() {

            @Override
            public void run() {
                //TO DO write block of code 
            }
        };

        timeoutBlock.addBlock(block);// execute the runnable block 

    } catch (Throwable e) {
        //catch the exception here . Which is block didn't execute within the time limit
    }
Нирошан Абейвикрама
источник
1

Я даю вам полный код. Вместо вызываемого мной метода вы можете использовать свой метод:

public class NewTimeout {
    public String simpleMethod() {
        return "simple method";
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        Callable<Object> task = new Callable<Object>() {
            public Object call() throws InterruptedException {
                Thread.sleep(1100);
                return new NewTimeout().simpleMethod();
            }
        };
        Future<Object> future = executor.submit(task);
        try {
            Object result = future.get(1, TimeUnit.SECONDS); 
            System.out.println(result);
        } catch (TimeoutException ex) {
            System.out.println("Timeout............Timeout...........");
        } catch (InterruptedException e) {
            // handle the interrupts
        } catch (ExecutionException e) {
            // handle other exceptions
        } finally {
            executor.shutdown(); // may or may not desire this
        }
    }
}
VickyCool
источник
0

Предположим, blockingMethodпросто поспите несколько миллисекунд:

public void blockingMethod(Object input) {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Мое решение заключается в использовании wait()и , synchronizedкак это:

public void blockingMethod(final Object input, long millis) {
    final Object lock = new Object();
    new Thread(new Runnable() {

        @Override
        public void run() {
            blockingMethod(input);
            synchronized (lock) {
                lock.notify();
            }
        }
    }).start();
    synchronized (lock) {
        try {
            // Wait for specific millis and release the lock.
            // If blockingMethod is done during waiting time, it will wake
            // me up and give me the lock, and I will finish directly.
            // Otherwise, when the waiting time is over and the
            // blockingMethod is still
            // running, I will reacquire the lock and finish.
            lock.wait(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Так ты можешь заменить

something.blockingMethod(input)

к

something.blockingMethod(input, 2000)

Надеюсь, поможет.

Euporie
источник