To main content

Die beste Alternative zu Thread.sleep() in Java

Veröffentlicht von Benjamin Marwell am

Falls Du Thread.sleep() entweder in Deinem Java-Code or Unit-Test verewndest, gibt ein Artikel von Marcio Endo wertvolle Tipps. Allerdings denke ich, dass es zwei sinnvolle Alternativen zu seinen Vorschlägen gibt.

Einleitung: vermeide Thread.sleep im Java-Code

In Marcios Beispiel musste man an der Originalklasse Änderungen vornehmen, um die Lauffähigkeit der Tests herzustellen. Meiner Meinung nach sollte man Produktions-Code nicht für Tests anpassen müssen — außer, er ist schlecht geschrieben oder man fügt lediglich getter und setter (vorzugsweise protected) ein. Mit dieser These im Hinterkopf, lasst uns nochmal die Original-Klasse vor Augen führen:

Keine Alternative: TimeUnit statt Thread.sleep()

Statt Thread.sleep(1000) in den Tests zu nutzen, kann man natürlich auch einfach diese einfachere API nutzen: TimeUnit#sleep(long timeout):

try {
  TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException intEx) {
  // handle... log or rethrow
}

Wie man aber sieht, muss man sich immer noch um das Exception-Handling kümmern und eine While-Schleife drum herum schreiben. Daher ist diese Methode kein Fortschritt gegenüber Thread.sleep(), sieht man einmal davon ab, dass sie bei größeren Timeouts minimal leichter zu lesen ist.

Alternative 1: Awaitility für Tests nutzen

Wenn man den Timeout nur im JUnit-Test benötigt, möchte man wie gesagt nicht extra seine bestehenden Klassen wesentlich anpassen. Mit dieser Vorraussetzung im Hinterkopf kann man auf ein kleines Tool (eine Library) ausweichen, die diese Logik im Test für einen übernimmt: awaitility.

Das hier ist der vollständige JUnit-Test:

Die Ausgabe ist: 22(ms).

Das schöne an dieser Lösung ist, dass man sich um Threading gar nicht kümmern muss. Außerdem unterstützt Awaitility in seinen APIs immer beide Aufrufe: Die Angabe von Duration sowie eine Methodensignatur mit long und TimeUnit.

Die Methode pollInterval() habe ich daher auch nur für dieses Beispiel benutzt. Zum einen sieht man hier die andere API, zum Anderen kommt man an etwas realistischere Werte: Der Default liegt vermutlich eher bei 100ms.

Diese winzige Maven-Dependency ist zudem sehr einfach verständlich. Ich nutze Awaitlity sehr oft in Maven-Modulen zum Testen, wenn ich in diesen Modulen auch async-Calls und Threading verwende.

Startet man den Thread darüber hinaus mit CompletableFuture, kann man die Thread-Management-Methode komplett aus dem Counter entfernen. Das kann ich nur empfehlen, da Thread-Management nicht in der Verantwortlichkeit einer Counter-Klasse liegen sollte.

Alternative 2: CompletableFutures nutzen

Wenn man aber im Produktions-Code mit Hintergrund-Operationen arbeitet, dann kann man bestehende Funktionalität aus der java.concurrent-API nehmen. Diese API ist prinzipiell auch in JakartaEE verfügbar, mit nur einer einzigen Anpassung — und diese ist zum Glück auch einfach in Erinnerung zu behalten.

CompletableFuture in einer JavaSE-Anwendung

Dieser Code gibt 33(ms) auf meinem Laptop aus.

Da wir Streams nutzen, ist hier der große Vorteil vorhanden, dass wir die Logik sehr einfach anpassen können. Man könnte etwa ein (completed) CompletableFuture als Fallback mit hinzufügen, um am Ende auf das Default hinter der .findFirst()-Methode verzichten zu können.

CompletableFutures in einer JakartaEE Web-Anwendung

Web-Anwendungen dürfen keine eigenen Threads starten, so steht es in der Spezifikation. Stattdessen muss man sich einen ManagedExecutorService als Feld injizieren lassen. Dieses übergibt man als zweiten Parameter an CompletableFuture#supplyAsync(). Das ist alles, was man beachten muss!

Fazit

Die beste alternative zu Thread.sleep() in Java ist die Vermeidung durch die Nutzung von entweder Awaitility in tests oder durch die Nutzung der java.concurrent-API in Produktions-Code.