¿Cómo llamo a algún método de bloqueo con un tiempo de espera en Java?

97

¿Existe una buena forma estándar de llamar a un método de bloqueo con un tiempo de espera en Java? Quiero poder hacer:

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

Si eso tiene sentido.

Gracias.

jjujuma
fuente
1
Como referencia, consulte Concurrencia de Java en la práctica por Brian Goetz págs. 126-134, específicamente la sección 6.3.7 "Poner límites de tiempo a las tareas"
marrón.2179

Respuestas:

151

Podrías usar un Ejecutor:

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
}

Si future.getno regresa en 5 segundos, lanza un TimeoutException. El tiempo de espera se puede configurar en segundos, minutos, milisegundos o cualquier unidad disponible como constante enTimeUnit .

Consulte JavaDoc para obtener más detalles.

skaffman
fuente
13
El método de bloqueo seguirá ejecutándose incluso después del tiempo de espera, ¿verdad?
Ivan Dubrov
1
Eso depende de future.cancel. Dependiendo de lo que esté haciendo el método de bloqueo en ese momento, puede o no terminar.
skaffman
4
¿Cómo puedo pasar el parámetro al método de bloqueo ()? ¡Gracias!
Robert A Henru
@RobertAHenru: Cree una nueva clase llamada BlockingMethodCallablecuyo constructor acepte los parámetros que desea pasar blockingMethod()y almacénelos como variables miembro (probablemente como finales). Luego, en el interior, call()pase esos parámetros al archivo blockMethod().
Vite Falcon
1
finalmente debería hacer future.cancel(true)- El método cancel (boolean) en el tipo Future <Object> no es aplicable para los argumentos ()
Noam Manos
6

Vea también TimeLimiter de Guava, que usa un Ejecutor detrás de escena.

Federico
fuente
1
Buena idea. Pero el vínculo está obsoleto. Googlecode ya no existe ... pruebe este enlace a SimpleTimeLimiter
Rhubarb
3

También hay una solución AspectJ para eso con la biblioteca jcabi-examples .

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

No puede ser más sucinto, pero debes depender de AspectJ e introducirlo en tu ciclo de vida de compilación, por supuesto.

Hay un artículo que lo explica con más detalle : Limitar el tiempo de ejecución del método Java

gvlasov
fuente
3

Es realmente genial que la gente intente implementar esto de tantas maneras. Pero la verdad es que NO hay forma.

La mayoría de los desarrolladores intentarían poner la llamada de bloqueo en un hilo diferente y tener un futuro o algún temporizador. PERO no hay forma en Java de detener un hilo externamente, y mucho menos en algunos casos muy específicos como los métodos Thread.sleep () y Lock.lockInterruptibly () que manejan explícitamente la interrupción del hilo.

Entonces, realmente tienes solo 3 opciones genéricas:

  1. Coloque su llamada de bloqueo en un nuevo hilo y si el tiempo expira, simplemente continúe, dejando ese hilo colgando. En ese caso, debe asegurarse de que el hilo esté configurado para ser un hilo Daemon. De esta manera, el hilo no evitará que su aplicación finalice.

  2. Utilice API de Java sin bloqueo. Entonces, para la red, por ejemplo, use NIO2 y use los métodos sin bloqueo. Para leer desde la consola, use Scanner.hasNext () antes de bloquear, etc.

  3. Si su llamada de bloqueo no es una IO, sino su lógica, entonces puede verificar repetidamente Thread.isInterrupted()si fue interrumpida externamente y tener otra llamada de hilo thread.interrupt()en el hilo de bloqueo

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

realmente recorre esos fundamentos si realmente desea comprender cómo funciona en Java. En realidad, habla sobre esas limitaciones y escenarios específicos, y cómo abordarlos en una de las conferencias.

Personalmente, trato de programar sin usar el bloqueo de llamadas tanto como sea posible. Existen kits de herramientas como Vert.x, por ejemplo, que hacen que sea realmente fácil y eficaz realizar operaciones de IO y no IO de forma asíncrona y sin bloqueo.

Espero que ayude

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

Tenga en cuenta que la parada está en desuso, la mejor alternativa es establecer una bandera booleana volátil, dentro deckingMethod () verifique y salga, así:

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
fuente
1

Prueba esto. Solución más sencilla. Garantiza que si el bloqueo no se ejecuta dentro del límite de tiempo. el proceso terminará y lanzará una excepción.

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

ejemplo:

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
fuente
1

Te estoy dando aquí el código completo. En lugar del método al que estoy llamando, puedes usar tu 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
fuente
0

Suponga que blockingMethodsolo duerme por algunos milis:

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

Mi solución es usar wait()y synchronizedasí:

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

Entonces puedes reemplazar

something.blockingMethod(input)

a

something.blockingMethod(input, 2000)

Espero eso ayude.

Euporie
fuente