Versprochen wird die Untersützung von Java 10.
https://www.eclipse.org/eclipse/news/4.7.3a/
https://www.eclipse.org/downloads/packages/release/oxygen/3a
Versprochen wird die Untersützung von Java 10.
https://www.eclipse.org/eclipse/news/4.7.3a/
https://www.eclipse.org/downloads/packages/release/oxygen/3a
Spring Retry ist ein Zusatzprojekt (https://github.com/spring-projects/spring-retry), um Codeblöcke wiederholt auszuführen, wenn sie zu einem Fehler führen.
Als erstes müssen zwei Dependencies in unsere POM:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
Schreiben wir eine Bean, die zwei Anläufe braucht, eine Operation durchzuführen.
@Component
class Achiever {
int tries = 1;
@Retryable
public int tryIt() {
System.out.println( "Runde " + tries );
tries++;
if ( tries != 3 )
throw new IllegalStateException( "Runde nicht 3" );
return tries;
}
}
Retryable drückt aus, das eine Operation bei Ausnahmen automatisch neu aufgerufen werden soll. |
|
Die Methoden können eine Rückgabe haben, oder auch keine. |
Damit Spring für die annotierten Methoden einen Proxy baut, müssen wir eine @Configuration
mit @EnableRetry
annotieren.
Wir können nun den Achiever
injizieren:
@Autowired Achiever archiever;
Und die Methode ausführen:
System.out.println( "Vor dem Aufruf" );
int i = archiever.tryIt();
System.out.println( "Nach dem Aufruf ist i=" + i );
Auf der Konsole erscheint keine Exception. Nur:
Vor dem Aufruf Runde 1 Runde 2 Nach dem Aufruf
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
String interceptor() default "";
Class<? extends Throwable>[] value() default {};
Class<? extends Throwable>[] include() default {};
Class<? extends Throwable>[] exclude() default {};
String label() default "";
boolean stateful() default false;
int maxAttempts() default 3;
String maxAttemptsExpression() default "";
Backoff backoff() default @Backoff();
String exceptionExpression() default "";
}
Die Anzahl der Wiederholungen ist nicht unendlich, Spring stoppt standardmäßig bei 3. |
Die Dokumentation unter https://github.com/spring-projects/spring-retry zeit weitere Möglichkeiten auf. Ein zusätzliches Beispiel:
@Retryable
public void tryIO() throws IOException {
throw new IOException( LocalTime.now().toString() );
}
@Recover
public void recover( IOException e ) {
System.err.println( "Aarrrrg: " + e );
}
Die mit @Recover annotierte Methode wird am Ende einer nicht erfolgreichen Aufrufkette aufgerufen. |
Bei Methoden, die idempotent sind, also zu einem Parameter immer den gleichen Wert liefern, kann Spring einen Cache zur Optimierung einsetzen.
Beispiele:
Achtung: Gewisse Cache-Inhalte können nach einer Zeit ungültig werden.
Um das Caching zu aktivieren annotiert man
@Configuration
mit @EnableCaching
public
(!!!) Methode mit @Cacheable
und vergibt einen Cache-Namen.@Component
class Hash {
@Cacheable( "md5" )
public byte[] md5( String text ) {
System.out.println( "hash: " + text );
try {
MessageDigest md = MessageDigest.getInstance( "MD5" );
return md.digest( text.getBytes( StandardCharsets.UTF_8 ) );
}
catch ( NoSuchAlgorithmException e ) {
throw new IllegalStateException( e );
}
}
}
Wir können Hash
injizieren
@Autowired
Hash hash;
und nutzen
byte[] md5_1 = hash.md5( "tutego" );
byte[] md5_2 = hash.md5( "tutego" );
System.out.println( Arrays.equals( md5_1, md5_2 ) ); // true
System.out.println( hash.getClass() ); // com.tutego.boot.basic.Hash$$EnhancerBySpringCGLIB$$4548965
KeyGenerator
en verwenden.@Cacheable(cacheNames="books", key="#isbn.rawNumber")
.@Cacheable(cacheNames="book", condition="#name.length() < 32")
@CachePut
annotiert, kann man den Cache selbst füllen, bzw. Werte überschreiben.@CacheEvict
annotiert, etwa @CacheEvict(cacheNames="books", allEntries=true) public void clear()
wird der Cache gelöscht.Das Spring Framework kann automatisch diverse Caching-Implementierung nutzen.
ConcurrentHashMap
.In der application.[properties|yml] lassen sich dann Dinge wie Lebensdauer, Größe, etc. extern konfigurieren.
Spring nutzt drei Annotationen, um Managed Beans mit ihren Zuständen und Operationen beim JMX Agent zu registrieren:
@ManagedResource
@ManagedAttribute
@ManagedOperation
package com.tutego.boot.actuator;
import java.util.HashMap;
import java.util.Map;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;
@Component
@ManagedResource
public class StringMapManagedBean {
private final Map<String, String> map = new HashMap<>();
@ManagedAttribute
public int getSize() {
return map.size();
}
@ManagedOperation
public String get( String key ) {
return map.get( key );
}
@ManagedOperation
public String put( String key, String value ) {
return map.put( key, value );
}
public void remove( String key ) {
map.remove( key );
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ManagedResource {
@AliasFor("objectName")
String value() default "";
@AliasFor("value")
String objectName() default "";
String description() default "";
int currencyTimeLimit() default -1;
boolean log() default false;
String logFile() default "";
String persistPolicy() default "";
int persistPeriod() default -1;
String persistName() default "";
String persistLocation() default "";
}
Spring Boot veröffentlicht selbst diverse Managed Beans unter der Domäne org.springframework.boot
.
org.springframework.boot:type=Endpoint,name=Health
zum Beispiel für die GesundheitsinformationenSie lassen sich abschalten mit management.endpoints.jmx.exposure.exclude=*
.
Falls mehrere ApplicationContext
exisistieren kommt es zum Namenskonflikt; der lässt sich mit management.endpoints.jmx.unique-names=true
vermeiden.
Der Domain-Name lässt sich anpassen mit management.endpoints.jmx.domain
.
Download unter http://www.oracle.com/technetwork/articles/javase/index-jsp-138363.html. Die Neuerungen fasse ich unter http://www.tutego.de/java/java-10-oracle-jdk-10-openjdk.html mit Links zusammen.
Details unter https://docs.gradle.org/4.6/release-notes.html
Infos unter http://wildfly.org/news/2018/02/28/WildFly12-Final-Released/. Wie auch Java 10 gibt es zeitbasierte Releasese: http://lists.jboss.org/pipermail/wildfly-dev/2017-December/006250.html.
Infos darüber unter https://netbeans.apache.org/download/nb90/index.html.
Allerdings frage ich mich, wer NB noch verwendet, ihr? Ich halte den NB-Zug für abgefahren. In den Insel kommen die Anmerkungen zu NB raus und IntelliJ stattdessen rein.
Die 9er Version kann kein Java 10, und die Entwickler freuen sich über Java 9-Support, wow, 1/2 Jahr nach dem Java 9 Release. In 2 Wochen kommt Java 10.
So schreibt es Oracle unter https://blogs.oracle.com/java-platform-group/the-future-of-javafx-and-other-java-client-roadmap-updates
Starting with JDK 11, Oracle is making JavaFX easier to adopt by making the technology available as a separate download, decoupled from the JDK. These changes clear the way for new contributors to engage in the open source OpenJFX community. Meanwhile, Oracle customers can benefit from continued commercial support for JavaFX in the Oracle JDK 8 through at least 2022.
With the Java Platform Module System in place since Java SE 9, it now more viable to decouple JavaFX from the JDK, in order to make it available as a separate download. This will make it easier for developers using JavaFX to have more freedom and flexibility with the framework. Moreover, with our focus on increasing the release cadence of OpenJDK, JavaFX needs to be able to move forward at a pace driven by the contributions from Oracle and others in the OpenJFX community. Oracle plans to implement this decoupling starting with Java 11 (18.9 LTS).
Präsentation und Code verlinkt unter https://blogs.oracle.com/java/jdbc-next%3a-a-new-asynchronous-api-for-connecting-to-a-database
Sehr spannend!
Lambda-Ausdrücke können lokale Variablen nur lesen und nicht beschreiben. Der Grund hat etwas mit der Art und Weise zu tun, wo Variablen gespeichert werden: Objekt- und statische Variablen „leben“ auf dem Heap lokale Variablen und Parameter „leben“ auf dem Stack. Wenn nun Threads ins Spiel kommen ist es nicht unüblich, dass unterschiedliche Threads die Variablen vom Heap nutzen; dafür gibt es Synchronisationsmöglichkeiten. Allerdings kann ein Thread nicht auf lokale Variablen eines anderen Threads zugreifen, denn ein Thread hat erst einmal keinen Zugriff auf den Stack-Speicher eines anderen Threads. Grundsätzlich wäre das möglich, allerdings wollten die Oracle-Entwickler diesen Pfad nicht gehen. Beim Lesezugriff wird tatsächlich eine Kopie angelegt, sodass sie für einen anderen Thread sichtbar ist.
Die Einschränkung, dass äußere lokale Variablen von Lambda-Ausdrücken nur gelesen werden können, ist an sich etwas Gutes, denn die Beschränkung minimiert Fehler bei nebenläufiger Ausführung von Lambda-Ausdrücken: arbeiten mehrere Threads Lambda-Ausdrücke ab, und beschreiben diese eine lokale Variable, müsste andernfalls eine Thread-Synchronisation her.
Grundsätzlich verbietet das Schreiben von lokalen Variablen aus Lambda-Ausdrücken heraus aber nicht jede Programmiersprache. In C# kann ein Lambda-Ausdruck lokale Variablen beschreiben, sie leben dann nicht mehr auf dem Stack.
Die invokeAll(…)-Methoden aus dem ExecutorService sind praktisch, wenn es darum geht, mehrere Aufgaben nebenläufig abzusenden, und später die Ergebnisse einzusammeln. Allerdings ist die Rückgabe vom Typ List<Future<T>> und wir werden nicht informiert, wenn ein Ergebnis vorliegt. Wir können zwar die Liste immer wieder ablaufen und jedes Future-Objekte mit isDone() fragen ob es fertig ist, aber das ist keine ideale Lösung.
Mit java.util.concurrent.CompletionService gibt es eine weitere Java-Schnittstelle – die keinen Basistyp erweitert – mit der wir ein Callable oder Runnable arbeiteten lassen können und später nacheinander die Ergebnisse einsammeln können, die fertig sind. Die Java-Bibliothek bringt mit ExecutorCompletionService eine Implementierung der Schnittstelle mit, die intern die fertigen Ergebnisse in einer Queue sammelt, und wir können die Queue abfragen. Schauen wir uns das in einem Beispiel an.
ExecutorService executor = Executors.newCachedThreadPool(); CompletionService<Integer> completionService = new ExecutorCompletionService<>( executor ); List.of( 4, 3, 2, 1 ).forEach( duration -> completionService.submit( () -> { TimeUnit.SECONDS.sleep( duration ); return duration; } ) ); for ( int i = 0; i < 4; i++ ) { try { System.out.println( completionService.take().get() ); } catch ( InterruptedException | ExecutionException e ) { e.printStackTrace(); } } executor.shutdown();
Der Typ ExecutorCompletionService erwartet im Konstruktor einen Executor, der den Code ausführen soll; wir setzen einen Thread-Pool ein. CompletionService hat zwei submit(…)-Methoden:
Abgesendet werden vier Callable-Exemplare, die 4, 3, 2, 1 Sekunden warten und ihre Wartezeit am Ende zurückgeben. Natürlich wird als erstes das Callable mit der Rückgabe 1 fertig, dann 2, usw.
Für die Rückgaben interessiert sich unser Programm nicht, denn es nutzt die take()-Methode. Insgesamt hat CompletionService drei Entnahme-Methoden:
Was der Schnittstelle fehlt ist eine Methode, die die verblendende Anzahl liefert. Wir müssen in unserem Code daher einen Zähler als extra Variable einführen.
Von der Intuition her ist von der Vererbung class Properties extends Hashtable<String, String> naheliegend, doch hat Sun nicht gewollt. Der Grund hat gar nicht so sehr mit Properties selbst zu tun, sondern mit der Basisklasse Hashtable und gewählten Umsetzung der Generics und der gewünschten Kompatibilität. Von Java 1.0 bis Java 1.4 hat die Klasse Hashtable eine Methode put(Object, Object). Ab Java 5 wird Object durch generische Typparameter ersetzt, sodass es in Hashtable heißt: put(K, V). Wenn nun Properties extends Hashtable<String,String> ist, dann wird die Hinzufügemethode zu put(String, Sting). Das wäre nicht mehr kompatibel zu put(Object, Object) vor Java 5. Also haben die Sun-Entwickler Properties extends Hashtable<Object, Object> geschrieben, auch wenn die put(…)-Methode überhaupt nicht verwendet werden soll, sondern setProperty(…). Da Generics nur ein „Trick“ des Compilers sind, lässt sich über den Raw-Typ tricksen:
Properties props = new Properties(); @SuppressWarnings( {"rawtypes", "unchecked"} ) Map<String, String> map = (Map) props;
Siehe https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes und https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide.
Es knarrzt an einigen Stellen, ich musste zum Beispiel meine CRUD-Implementierungen anpassen, die Methodennamen haben sich geändert. Wie sind eure Erfahrungen?
Sich einen Iterator von einem Stream geben zu lassen ist nützlich, weil dann der Zeitpunkt des Konsumierens vom Zeitpunkt des Stream-Aufbaus getrennt werden kann. Es ist nicht einfach mehrere Streams gleichzeitig abzulaufen, doch mit Iteratoren funktioniert das. Zudem lässt sich in Stream mit einer Generator-Funktion einfach aufbauen, in Iterator aber nicht.
Beispiel: Aus zwei Streams sollen jeweils alterrnierend das nächste Element konsumiert und ausgegeben werden.
Iterator<Integer> iterator1 = Stream.of( 1, 3, 5 ).iterator(); Iterator<Integer> iterator2 = Stream.of( 2, 4, 6, 7, 8 ).iterator(); while ( iterator1.hasNext() || iterator2.hasNext() ) { if ( iterator1.hasNext() ) System.out.println( iterator1.next() ); if ( iterator2.hasNext() ) System.out.println( iterator2.next() ); }
Iterator ist ein Datentyp, der häufig in der Rückgabe verwendet wird, seltener als Parametertyp. Mit stream.iterator() ist es aber möglich, die Daten vom Stream genau an solchen Stellen zu übergeben. Einen Stream in einen Iterator zu konvertieren, um diesen dann mit hasNext()/next() abzulaufen, ist wenig sinnvoll, hierfür bietet sich genauso gut forEach(…) auf dem Stream an.
Der Typ Stream bietet eine Methode iterator(), erweitert jedoch die Schnittstelle Iterable nicht. In der Javadoc ist bei iterator() die Bemerkung „terminale Operation“ vermerkt, denn der Iterator saugt den Stream leer, sodass ein zweiter iterator()-Aufruf auf einem Stream nicht möglich ist. Bei Klassen, die Iterable implementieren, muss ein Aufruf von iterator() beliebig oft möglich sein. Bei Streams ist das nicht gegeben, da die Streams selbst nicht für die Daten stehen wie eine Collection, die daher Iterable ist.
Ist auf der anderen Seite ein Iterator gegeben, lässt sich dieser nicht direkt in einen Stream bringen. Iteratoren können wie Streams unendlich sein, und es gibt keinen Weg, die Daten eines Iterators als Quelle zu benutzen. Natürlich ist es möglich, den Iterator abzulaufen und daraus einen neuen Stream aufzubauen, dann muss der Iterator aber endlich sein.
Beispiel: Die Methode ImageIO.getImageReadersBySuffix(String) liefert einen Iterator von ImageReader-Objekten – sie sollen über einen Strom zugänglich sein:
Builder<ImageReader> builder = Stream.builder(); for ( Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix( "jpg" ); iter.hasNext(); ) builder.add( iter.next() ); Stream<ImageReader> stream = builder.build(); System.out.println( stream.count() ); // 1
Einen anderen Weg geht StreamSupport.
Ist eine alte Enumeration gegeben, hilft Collections.list(enumeration).stream(), denn list(…) liefert eine ArrayList mit allen Einträgen; list(Iterator) gibt es da hingegen nicht.
Die Schnittstelle Stream deklariert keine abstrakte iterator()-Methode, sondern bekommt die Methode vom Obertyp BaseStream vererbt. BaseStream ist insgesamt Basistyp von:
Alle diese drei Typen haben damit iterator()-Methdoden, doch die Rückgaben sind unterschiedlich:
Typ | iterator()-Methdoden und Rückgabe |
Stream<T> | Iterator<T> iterator() |
DoubleStream | PrimitiveIterator.OfDouble iterator() |
IntStream | PrimitiveIterator.OfInt iterator() |
LongStream | PrimitiveIterator.OfInt iterator() |
Unterschiedliche Rückgaben der Iteratoren
PrimitiveIterator ist eine Schnittstelle aus dem Paket java.util mit eben drei inneren statischen Schnittstellen: OfDouble, OfInt und OfLong, die PrimitiveIterator erweiten. Jeder dieser drei inneren Typen hat eigene, aber symmetrische Methoden:
Statt WrapperTyp und PrimitiverTyp ist dann Double, Integer, Long und double, int, double einzusetzen.
Beispiel: Ein vom Stream abgeleiter Iterator besorgt Zahlen. Diese werden in einem anderen Stream eingesetzt.
PrimitiveIterator.OfInt counter = IntStream.iterate( 1, Math::incrementExact ).iterator(); Stream.of( "Telegram", "WhatsApp", "Facebook Messenger", "Insta" ) .map( s -> counter.nextInt() + ". " + s ) .forEach( System.out::println );
Die größte Änderung ist, was angekündigt wurde:
These builds include JEP 320 (Remove the Java EE and CORBA Modules) [1], so they're significantly smaller (nine fewer modules, 22 fewer megabytes on Linux/x64). - Mark
Weitere Infos und Download unter http://jdk.java.net/11/. Folgende Änderungen gibt es:
Changeset | Bug ID | Synopsis |
d9fce53461a1 | 8197812 | (ref) Data race in Finalizer |
c38163717870 | 8198227 | Fix COMPARE_BUILD after forest consolidation |
329428e095b6 | 8198306 | Add post custom extension hooks to two launchers |
67cdc215ed70 | 8198228 | Spec clarification: j.u.Locale.getDisplayName() |
edaa989b4e67 | 8058965 | Remove IPv6 support from TwoStacksPlainSocketImpl [win] |
01237b276b8b | 8198318 | Make build comparisons clean again |
7f9c3cd11e97 | 8170120 | jimage throws IOException when the given file is not a jimage file |
0a185d6fafa1 | 8198379 | tools/jimage/JImageListTest.java failing |
b417304c811b | 8198380 | tools/jimage/JImageExtractTest.java failing |
42cec55157fa | 8198417 | Exclude tools/jimage/JImageExtractTest.java and tools/jimage/JImageListTest.java on Windows |
37beaca49e63 | 8194922 | jlink –exclude-resources should never exclude module-info.class |
18debf414948 | 8198425 | make/Main.gmk Add extra extension/override points to the make file |
c7e84c0a51c3 | 8198328 | Create devkit for Solaris with developer studio 12.6 and Solaris11.3 |
916690b5edc9 | 8194892 | add compiler support for local-variable syntax for lambda parameters |
576e024f10b6 | 8198418 | Invoke LambdaMetafactory::metafactory exactly from the BootstrapMethodInvoker |
906025796009 | 8198441 | Replace native Runtime::runFinalization0 method with shared secrets |
b75c9e2e3b1f | 8198450 | Make jdk.internal.vm.compiler/module-info.java.extra reproducable |
1913e7fc6be9 | 8198301 | jdk11+1 was built as ‚fcs‘ instead of ‚ea‘ |
b2bba53b079d | 8198303 | jdk11+1 was build with incorrect GA date as 2018-03-20 |
02404e27d356 | 8198479 | JDK build is broken by 8194892 |
847a988152b8 | 8194154 | System property user.dir should not be changed |
cc30928a834e | 8198385 | Remove property sun.locale.formatasdefault |
28d8fc8cd3cd | 8190904 | Incorrect currency instance returned by java.util.Currency.getInstance() |
b1a5b4ad7427 | 8198523 | Refactor BootstrapMethodInvoker to further avoid runtime type checks |
b25eb74ec283 | 8197439 | Crash with -XDfind=lambda for anonymous class in anonymous class. |
9e3f2ec326ba | 8198502 | Exception at runtime due to lambda analyzer reattributes live AST |
20358c9f72c1 | 8198563 | Test langtools/tools/javac/analyzer/AnonymousInAnonymous.java failing after JDK-8198502 |
03ae177c26b0 | 8198512 | compiler support for local-variable syntax for lambda parameters |
ThreadPoolExecutor should not specify a dependency on finalizationPrevious versions of ThreadPoolExecutor had a finalize method that shut down the thread pool, but in this version the finalize method does nothing. This should have no visible effect unless a subclass explicitly invokes the finalize method and relies on the executor being shutdown.
jarsigner should print when a timestamp will expireThe jarsigner tool now shows more information about the lifetime of a timestamped JAR. New warning and error messages are displayed when a timestamp has expired or is expiring within one year.
Details zu den Neuerungen unter https://junit.org/junit5/docs/5.1.0/release-notes/index.html#release-notes-5.1.0
Die Klasse java.util.concurrent.FutureTask implementiert Future, Runnable und RunnableFuture und wird intern von der Java-Bibliothek verwendet, wenn wir mit submit(…) etwas beim ExecutorService absetzen. Auch wir können den Typ direkt als Wrapper um ein Callable oder Runnable nutzen, denn es gibt praktische Callback-Methoden, die wir überschreiben können, etwa done(), wenn eine Berechung fertig ist.
Dazu ein Beispiel: Ein Callable liefert den Namen des Benutzers. Ein FutureTask legt sich um dieses Callable und bekommt mit, wann das Callable fertig ist und modifiziert dann den Benutzernamen und gibt weiterhin eine Meldung aus.
Callable<String> username = () -> System.getProperty( "user.name" ); FutureTask<String> wrappedUsername = new FutureTask<>( username ) { @Override protected void done() { try { System.out.printf( "done: isDone=%s, isCancelled=%s%n", isDone(), isCancelled() ); System.out.println( "done: get=" + get() ); } catch ( InterruptedException | ExecutionException e ) { /* Ignore */ } } @Override protected void set( String v ) { System.out.println( "set: " + v ); super.set( v.toUpperCase() ); } }; ExecutorService scheduler = Executors.newCachedThreadPool(); scheduler.submit( wrappedUsername ); System.out.println( "main: " + wrappedUsername.get() ); scheduler.shutdown();
Wichtig in der Nutzung ist, nicht die Rückgabe vom submit(…) auszuwerten, was wir normalerweise machen, sondern das übergebene FutureTask zu erfragen.
Die Ausgaben vom Programm sind oft ein wenig durcheinander:
set: Christian done: isDone=true, isCancelled=false done: get=CHRISTIAN main: CHRISTIAN
Die Reihenfolge in den Aufrufen ist immer so: Der FutureTask stellt die Fertigstellung vom Callable fest und ruft set(…) auf. Anschließend done().
Die Klasse CountDownLatch hilft Threads an einem gewissen Punkt zusammenzukommen und dann weiterzumachen. Das kann ein Thread sein, der auf das Fertigwerden von N anderen Threads wartet oder N Threads, die ein Signal zum Loslaufen bekommen. CountDownLatch ist mit einem Zähler assoziiert, der sich herunterzählen lässt. Auf der anderen Seite wartet die andere Partei darauf, dass der Zähler 0 wird, um fortzuführen. Mit diesem Wissen ergibt auch der Klassenname Sinn: „count down“ heißt auf deutsch „runterzählen“ und „latch“ ist das englische Wort für Falle, Riegel, Sperrklinke; nach dem erfolgreichen runterzählen entsperrt die Klinke.
Ein Beispiel. Wir wollen prüfen, ob Hosts im Internet erreichbar sind. Der Zähler vom CountDownLatch entspricht der Anzahl der zu überprüfenden Hosts. Für jeden Host starten wir einen Thread. Ist der Host erreichbar, wird CountDownLatch mit countDown() herunterzählt. Am Ende waren wir jedoch nicht mit await() ob wir bei 0 angekommen sind – Hosts können nicht erreichbar sein – sondern wir warten eine gewisse Zeit mit der überladenen Methode await(long timeout, TimeUnit unit). Ist der Zähler nach der Zeit nicht 0 gibt es keine Ausnahme, sondern wir erfragen den Zähler um herauszufinden, wie viele Hosts erreichbar waren.
List<String> hosts = List.of( "popel.nase", "tutego.de" ); CountDownLatch latch = new CountDownLatch( hosts.size() ); for ( String host : hosts ) { new Thread( () -> { try { if ( InetAddress.getByName( host ).isReachable( 900 /* ms */ ) ) latch.countDown(); } catch ( IOException e ) { /* ignore */ } } ).start(); } try { if ( latch.await( 1, TimeUnit.SECONDS ) ) System.out.println( "Alle Hosts erreicht" ); System.out.printf( "%d von %d Hosts nicht erreicht", latch.getCount(), hosts.size() ); } catch ( InterruptedException e ) { e.printStackTrace(); }
Der CountDownLatch hat nur eine Methode zum Herunterzählen, nicht zum wieder erhöhen. Damit ist klar, dass sich ein heruntergezähltes Objekt nicht mehr verwenden kann.