Como chamo algum método de bloqueio com tempo limite em Java?

97

Existe uma maneira legal padrão de chamar um método de bloqueio com um tempo limite em Java? Eu quero ser capaz de fazer:

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

se isso faz sentido.

Obrigado.

Jjujuma
fonte
1
Como referência, verifique o Java Concurrency in Practice por Brian Goetz pp.
126-134

Respostas:

151

Você poderia usar um 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
}

Se future.getnão retornar em 5 segundos, ele gerará um TimeoutException. O tempo limite pode ser configurado em segundos, minutos, milissegundos ou qualquer unidade disponível como uma constante em TimeUnit.

Veja o JavaDoc para mais detalhes.

skaffman
fonte
13
O método de bloqueio continuará a funcionar mesmo após o tempo limite, certo?
Ivan Dubrov
1
Isso depende de future.cancel. Dependendo do que o método de bloqueio está fazendo no momento, ele pode ou não ser encerrado.
skaffman
4
como posso passar o parâmetro para o blockingMethod ()? Obrigado!
Robert A Henru
@RobertAHenru: Crie uma nova classe chamada BlockingMethodCallablecujo construtor aceita os parâmetros que você deseja passar blockingMethod()e armazene-os como variáveis ​​de membro (provavelmente como finais). Em seguida, call()passe esses parâmetros para o blockMethod().
Vite Falcon
1
finalmente deve fazer future.cancel(true)- O método cancel (boolean) no tipo Future <Object> não é aplicável para os argumentos ()
Noam Manos
9

Você poderia envolver a chamada em a FutureTaske usar a versão de tempo limite de get ().

Consulte http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/FutureTask.html

Colin Goudie
fonte
1
FutureTask em si não é assíncrono, certo? Por si só, ele faz as coisas de forma síncrona, você precisa combiná-lo com um Executor para gerar um comportamento assíncrono.
Skaffman
6

Veja também TimeLimiter da Guava, que usa um Executor nos bastidores.

Federico
fonte
1
Boa ideia. Mas o link está obsoleto. Googlecode não existe mais .. tente este link para SimpleTimeLimiter
Rhubarb
3

Também existe uma solução AspectJ para isso com a biblioteca de aspectos jcabi .

@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
}

Não pode ser mais sucinto, mas você tem que depender do AspectJ e introduzi-lo em seu ciclo de vida de construção, é claro.

Há um artigo que explica isso melhor: Limite o tempo de execução do método Java

gvlasov
fonte
3

É muito bom que as pessoas tentem implementar isso de tantas maneiras. Mas a verdade é que NÃO há como.

A maioria dos desenvolvedores tentaria colocar a chamada de bloqueio em um thread diferente e teria um futuro ou algum temporizador. MAS não há como em Java parar um thread externamente, muito menos alguns casos muito específicos como os métodos Thread.sleep () e Lock.lockInterruptibly () que tratam explicitamente da interrupção do thread.

Então, na verdade, você tem apenas 3 opções genéricas:

  1. Coloque sua chamada de bloqueio em um novo tópico e, se o tempo expirar, siga em frente, deixando o tópico suspenso. Nesse caso, você deve certificar-se de que o thread está definido como um thread do Daemon. Dessa forma, o thread não interromperá o encerramento do seu aplicativo.

  2. Use APIs Java sem bloqueio. Portanto, para rede, por exemplo, use NIO2 e use os métodos sem bloqueio. Para ler a partir do console, use Scanner.hasNext () antes de bloquear etc.

  3. Se sua chamada de bloqueio não for um IO, mas sua lógica, você pode verificar repetidamente para Thread.isInterrupted()verificar se foi interrompido externamente e ter outra chamada de thread thread.interrupt()no thread de bloqueio

Este curso sobre simultaneidade https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

realmente percorre esses fundamentos se você realmente deseja entender como funciona em Java. Na verdade, fala sobre essas limitações e cenários específicos e como abordá-los em uma das palestras.

Eu pessoalmente tento programar sem usar o máximo possível de chamadas de bloqueio. Existem kits de ferramentas como o Vert.x, por exemplo, que tornam realmente fácil e eficiente fazer IO e nenhuma operação IO de forma assíncrona e sem bloqueios.

Espero que ajude

Michael P
fonte
1
Thread thread = new Thread(new Runnable() {
    public void run() {
        something.blockingMethod();
    }
});
thread.start();
thread.join(2000);
if (thread.isAlive()) {
    thread.stop();
}

Observe que a parada está obsoleta, a melhor alternativa é definir algum sinalizador booleano volátil, dentro de BlockMethod (), verifique e saia, assim:

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)");
        }
    }
}
jnr
fonte
1

Experimente isso. Solução mais simples. Garante que se o bloco não for executado dentro do limite de tempo. o processo será encerrado e lançará uma exceção.

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;
    }
}

exemplo:

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
    }
Niroshan Abeywickrama
fonte
1

Estou dando aqui o código completo. No lugar do método que estou chamando, você pode usar seu método:

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
fonte
0

Suponha que blockingMethodapenas durma por alguns milis:

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

Minha solução é usar wait()e synchronizedassim:

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();
        }
    }
}

Então você pode substituir

something.blockingMethod(input)

para

something.blockingMethod(input, 2000)

Espero que ajude.

Euporie
fonte
0

Você precisa de uma implementação de disjuntor como a presente no projeto à prova de falhas no GitHub.

Marco Montel
fonte