Ist es möglich, Java zu zwingen, eine Ausnahme auszulösen, nachdem ein Codeblock länger als akzeptabel ausgeführt wurde?
Antworten:
Ja, aber es ist im Allgemeinen eine sehr schlechte Idee, einen anderen Thread zu zwingen, in einer zufälligen Codezeile zu unterbrechen. Sie würden dies nur tun, wenn Sie beabsichtigen, den Prozess herunterzufahren.
Was Sie tun können, ist, Thread.interrupt()
nach einer bestimmten Zeit für eine Aufgabe zu verwenden . Wenn der Code dies nicht überprüft, funktioniert es jedoch nicht. Ein ExecutorService kann dies mit vereinfachenFuture.cancel(true)
Es ist viel besser, wenn der Code sich selbst zeitlich festlegt und anhält, wenn es nötig ist.
Hier ist der einfachste Weg, den ich kenne, um dies zu tun:
final Runnable stuffToDo = new Thread() {
@Override
public void run() {
/* Do stuff here. */
}
};
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future future = executor.submit(stuffToDo);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
future.get(5, TimeUnit.MINUTES);
}
catch (InterruptedException ie) {
/* Handle the interruption. Or ignore it. */
}
catch (ExecutionException ee) {
/* Handle the error. Or ignore it. */
}
catch (TimeoutException te) {
/* Handle the timeout. Or ignore it. */
}
if (!executor.isTerminated())
executor.shutdownNow(); // If you want to stop the code that hasn't finished.
Alternativ können Sie eine TimeLimitedCodeBlock-Klasse erstellen, um diese Funktionalität zu verpacken, und sie dann überall dort verwenden, wo Sie sie benötigen:
new TimeLimitedCodeBlock(5, TimeUnit.MINUTES) { @Override public void codeBlock() {
// Do stuff here.
}}.run();
executor.shutdownNow()
möchten Sie wahrscheinlich while (true) {try {if (executor.awaitTermination(1, TimeUnit.SECONDS)) break;} catch (InterruptedException ie) {}}
, da das tatsächliche Stoppen der Aufgabe einige Zeit dauern kann.
Ich habe einige der anderen Antworten in einer einzigen Dienstprogrammmethode zusammengefasst:
public class TimeLimitedCodeBlock {
public static void runWithTimeout(final Runnable runnable, long timeout, TimeUnit timeUnit) throws Exception {
runWithTimeout(new Callable<Object>() {
@Override
public Object call() throws Exception {
runnable.run();
return null;
}
}, timeout, timeUnit);
}
public static <T> T runWithTimeout(Callable<T> callable, long timeout, TimeUnit timeUnit) throws Exception {
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future<T> future = executor.submit(callable);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
return future.get(timeout, timeUnit);
}
catch (TimeoutException e) {
//remove this if you do not want to cancel the job in progress
//or set the argument to 'false' if you do not want to interrupt the thread
future.cancel(true);
throw e;
}
catch (ExecutionException e) {
//unwrap the root cause
Throwable t = e.getCause();
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new IllegalStateException(t);
}
}
}
}
Beispielcode, der diese Dienstprogrammmethode verwendet:
public static void main(String[] args) throws Exception {
final long startTime = System.currentTimeMillis();
log(startTime, "calling runWithTimeout!");
try {
TimeLimitedCodeBlock.runWithTimeout(new Runnable() {
@Override
public void run() {
try {
log(startTime, "starting sleep!");
Thread.sleep(10000);
log(startTime, "woke up!");
}
catch (InterruptedException e) {
log(startTime, "was interrupted!");
}
}
}, 5, TimeUnit.SECONDS);
}
catch (TimeoutException e) {
log(startTime, "got timeout!");
}
log(startTime, "end of main method!");
}
private static void log(long startTime, String msg) {
long elapsedSeconds = (System.currentTimeMillis() - startTime);
System.out.format("%1$5sms [%2$16s] %3$s\n", elapsedSeconds, Thread.currentThread().getName(), msg);
}
Ausgabe vom Ausführen des Beispielcodes auf meinem Computer:
0ms [ main] calling runWithTimeout!
13ms [ pool-1-thread-1] starting sleep!
5015ms [ main] got timeout!
5016ms [ main] end of main method!
5015ms [ pool-1-thread-1] was interrupted!
runWithTimeout
Methode ausführe , auch wenn ich das Zeitlimit auf 150 Minuten festgelegt habe.
Wenn es sich um Testcode handelt, den Sie zeitlich festlegen möchten, können Sie das folgende time
Attribut verwenden:
@Test(timeout = 1000)
public void shouldTakeASecondOrLess()
{
}
Wenn es sich um Produktionscode handelt, gibt es keinen einfachen Mechanismus. Welche Lösung Sie verwenden, hängt davon ab, ob Sie den zeitgesteuerten Code ändern können oder nicht.
Wenn Sie den zeitgesteuerten Code ändern können, besteht ein einfacher Ansatz darin, Ihren zeitgesteuerten Code an die Startzeit und in regelmäßigen Abständen an die aktuelle Zeit zu erinnern. Z.B
long startTime = System.currentTimeMillis();
// .. do stuff ..
long elapsed = System.currentTimeMillis()-startTime;
if (elapsed>timeout)
throw new RuntimeException("tiomeout");
Wenn der Code selbst nicht nach Zeitüberschreitung suchen kann, können Sie den Code in einem anderen Thread ausführen und auf den Abschluss oder die Zeitüberschreitung warten.
Callable<ResultType> run = new Callable<ResultType>()
{
@Override
public ResultType call() throws Exception
{
// your code to be timed
}
};
RunnableFuture future = new FutureTask(run);
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(future);
ResultType result = null;
try
{
result = future.get(1, TimeUnit.SECONDS); // wait 1 second
}
catch (TimeoutException ex)
{
// timed out. Try to stop the code if possible.
future.cancel(true);
}
service.shutdown();
}
Ich kann zwei Optionen vorschlagen.
Fügen Sie innerhalb der Methode ein lokales Feld hinzu und testen Sie die Zeit jedes Mal um die Schleife, vorausgesetzt, es handelt sich um eine Schleife und wartet nicht auf ein externes Ereignis.
void method() {
long endTimeMillis = System.currentTimeMillis() + 10000;
while (true) {
// method logic
if (System.currentTimeMillis() > endTimeMillis) {
// do some clean-up
return;
}
}
}
Führen Sie die Methode in einem Thread aus und lassen Sie den Anrufer bis 10 Sekunden zählen.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
method();
}
});
thread.start();
long endTimeMillis = System.currentTimeMillis() + 10000;
while (thread.isAlive()) {
if (System.currentTimeMillis() > endTimeMillis) {
// set an error flag
break;
}
try {
Thread.sleep(500);
}
catch (InterruptedException t) {}
}
Der Nachteil dieses Ansatzes besteht darin, dass method () einen Wert nicht direkt zurückgeben kann, sondern ein Instanzfeld aktualisieren muss, um seinen Wert zurückzugeben.
Thread.join(long)
mit einer Auszeit zu machen;)
thread.start()
Sie thread.join(10000);
anstelle des restlichen Codes haben könnten .
EDIT: Peter Lawrey hat vollkommen recht: Es ist nicht so einfach wie das Unterbrechen eines Threads (mein ursprünglicher Vorschlag), und Executors & Callables sind sehr nützlich ...
Anstatt Threads zu unterbrechen, können Sie eine Variable für Callable festlegen, sobald das Timeout erreicht ist. Der Callable sollte diese Variable an geeigneten Punkten in der Taskausführung überprüfen, um zu wissen, wann sie zu stoppen ist.
Callables geben Futures zurück, mit denen Sie eine Zeitüberschreitung festlegen können, wenn Sie versuchen, das Ergebnis der Zukunft zu erhalten. Etwas wie das:
try {
future.get(timeoutSeconds, TimeUnit.SECONDS)
} catch(InterruptedException e) {
myCallable.setStopMeAtAppropriatePlace(true);
}
Siehe Future.get, Executors und Callable ...
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html
Ich habe eine sehr einfache Lösung ohne Verwendung von Frameworks oder APIs erstellt. Das sieht eleganter und verständlicher aus. Die Klasse heißt TimeoutBlock.
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;
}
}
Beispiel:
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 to execute
}
};
timeoutBlock.addBlock(block);// execute the runnable block
} catch (Throwable e) {
//catch the exception here . Which is block didn't execute within the time limit
}
Dies war sehr nützlich für mich, als ich mich mit einem FTP-Konto verbinden musste. Dann laden Sie Sachen herunter und laden Sie sie hoch. Manchmal hängt die FTP-Verbindung oder bricht vollständig ab. Dies führte zum Ausfall des gesamten Systems. und ich brauchte einen Weg, um es zu erkennen und zu verhindern, dass es passiert. Also habe ich das erstellt und benutzt. Funktioniert ziemlich gut.
Wenn Sie eine CompletableFuture-Methode wünschen, können Sie eine Methode wie verwenden
public MyResponseObject retrieveDataFromEndpoint() {
CompletableFuture<MyResponseObject> endpointCall
= CompletableFuture.supplyAsync(() ->
yourRestService.callEnpoint(withArg1, withArg2));
try {
return endpointCall.get(10, TimeUnit.MINUTES);
} catch (TimeoutException
| InterruptedException
| ExecutionException e) {
throw new RuntimeException("Unable to fetch data", e);
}
}
Wenn Sie spring verwenden, können Sie die Methode mit einem versehen, @Retryable
sodass die Methode dreimal wiederholt wird, wenn eine Ausnahme ausgelöst wird.
Anstatt die Aufgabe im neuen Thread und den Timer im Hauptthread zu haben, sollten Sie den Timer im neuen Thread und die Aufgabe im Hauptthread haben:
public static class TimeOut implements Runnable{
public void run() {
Thread.sleep(10000);
if(taskComplete ==false) {
System.out.println("Timed Out");
return;
}
else {
return;
}
}
}
public static boolean taskComplete = false;
public static void main(String[] args) {
TimeOut timeOut = new TimeOut();
Thread timeOutThread = new Thread(timeOut);
timeOutThread.start();
//task starts here
//task completed
taskComplete =true;
while(true) {//do all other stuff }
}
TimeOut timeOut
in der main()
Methode wird niemals verwendet.
Thread timeOutThread = new Thread(timeOut);
dann @Johannes timeOut verwendet werden.
Es gibt einen hackigen Weg, dies zu tun.
Legen Sie ein boolesches Feld fest, um anzugeben, ob die Arbeit abgeschlossen wurde. Stellen Sie dann vor dem Codeblock einen Timer ein, um nach Ihrer Zeitüberschreitung einen Code auszuführen. Der Timer prüft, ob die Ausführung des Codeblocks abgeschlossen ist, und löst andernfalls eine Ausnahme aus. Sonst wird es nichts tun.
Das Ende des Codeblocks sollte natürlich das Feld auf true setzen, um anzuzeigen, dass die Arbeit erledigt wurde.
Ich hatte ein ähnliches Problem, bei dem es meine Aufgabe war, innerhalb eines bestimmten Zeitlimits eine Nachricht an SQS zu senden. Ich habe die triviale Logik verwendet, es über einen anderen Thread auszuführen und auf sein zukünftiges Objekt zu warten, indem ich das Zeitlimit angegeben habe. Dies würde mir bei Timeouts eine TIMEOUT-Ausnahme geben.
final Future<ISendMessageResult> future =
timeoutHelperThreadPool.getExecutor().submit(() -> {
return getQueueStore().sendMessage(request).get();
});
try {
sendMessageResult = future.get(200, TimeUnit.MILLISECONDS);
logger.info("SQS_PUSH_SUCCESSFUL");
return true;
} catch (final TimeoutException e) {
logger.error("SQS_PUSH_TIMEOUT_EXCEPTION");
}
Es gibt jedoch Fälle, in denen Sie nicht verhindern können, dass der Code von einem anderen Thread ausgeführt wird, und in diesem Fall echte Negative erhalten.
Beispiel: In meinem Fall hat meine Anforderung SQS erreicht, und während die Nachricht gesendet wurde, hat meine Codelogik das angegebene Zeitlimit festgestellt. In Wirklichkeit wurde meine Nachricht in die Warteschlange verschoben, aber mein Hauptthread ging davon aus, dass sie aufgrund der TIMEOUT-Ausnahme fehlgeschlagen ist. Dies ist eine Art Problem, das vermieden und nicht gelöst werden kann. Wie in meinem Fall habe ich es vermieden, indem ich eine Zeitüberschreitung angegeben habe, die in fast allen Fällen ausreichen würde.
Wenn sich der Code, den Sie unterbrechen möchten, in Ihrer Anwendung befindet und nicht wie ein API-Aufruf ist, können Sie ihn einfach verwenden
future.cancel(true)
Denken Sie jedoch daran, dass Java Docs angibt, dass die Ausführung blockiert wird.
"Versuche, die Ausführung dieser Aufgabe abzubrechen. Dieser Versuch schlägt fehl, wenn die Aufgabe bereits abgeschlossen wurde, bereits abgebrochen wurde oder aus einem anderen Grund nicht abgebrochen werden konnte. Wenn dies erfolgreich ist und diese Aufgabe beim Abbruch nicht gestartet wurde, wird dies ausgeführt Die Task sollte niemals ausgeführt werden. Wenn die Task bereits gestartet wurde, bestimmt der Parameter mayInterruptIfRunning, ob der Thread, der diese Task ausführt, unterbrochen werden soll, um die Task zu stoppen. "