Hoe u een discussielijn start in Java

1. Inleiding

In deze tutorial gaan we verschillende manieren verkennen om een ​​thread te starten en parallelle taken uit te voeren.

Dit is erg handig, met name bij lange of terugkerende bewerkingen die niet op de hoofdthread kunnen worden uitgevoerd, of waar de UI-interactie niet in de wacht kan worden gezet tijdens het wachten op de resultaten van de bewerking.

Lees zeker onze tutorial over de levenscyclus van een thread in Java voor meer informatie over de details van threads.

2. De basisprincipes van het voeren van een discussielijn

We kunnen gemakkelijk wat logica schrijven die in een parallelle thread wordt uitgevoerd door de Draad kader.

Laten we een eenvoudig voorbeeld proberen door de extensie Draad klasse:

openbare klasse NewThread breidt Thread {public void run () {long startTime = System.currentTimeMillis (); int i = 0; while (true) {System.out.println (this.getName () + ": nieuwe thread wordt uitgevoerd ..." + i ++); probeer {// Wacht een seconde zodat het niet te snel afdrukt Thread.sleep (1000); } catch (InterruptedException e) {e.printStackTrace (); } ...}}}

En nu schrijven we een tweede klas om te initialiseren en onze thread te starten:

openbare klasse SingleThreadExample {openbare statische leegte hoofd (String [] args) {NewThread t = nieuwe NewThread (); t.start (); }}

We moeten de begin() methode op threads in de NIEUW state (het equivalent van niet gestart). Anders genereert Java een exemplaar van IllegalThreadStateException uitzondering.

Laten we nu aannemen dat we meerdere threads moeten starten:

openbare klasse MultipleThreadsExample {openbare statische leegte hoofd (String [] args) {NewThread t1 = nieuwe NewThread (); t1.setName ("MyThread-1"); NewThread t2 = nieuwe NewThread (); t2.setName ("MyThread-2"); t1.start (); t2.start (); }}

Onze code ziet er nog steeds vrij eenvoudig uit en lijkt erg op de voorbeelden die we online kunnen vinden.

Natuurlijk, dit is verre van productieklare code, waarbij het van cruciaal belang is om bronnen op de juiste manier te beheren, om te veel contextwisseling of te veel geheugengebruik te vermijden.

Dus om productieklaar te krijgen, moeten we nu een extra boilerplate schrijven omgaan met:

  • de consistente creatie van nieuwe threads
  • het aantal gelijktijdige live threads
  • the threads deallocation: erg belangrijk voor daemon-threads om lekken te voorkomen

Als we willen, kunnen we onze eigen code schrijven voor al deze case-scenario's en zelfs meer, maar waarom zouden we het wiel opnieuw uitvinden?

3. Het ExecutorService Kader

De ExecutorService implementeert het Thread Pool-ontwerppatroon (ook wel een gerepliceerd worker- of worker-crew-model genoemd) en zorgt voor het threadbeheer dat we hierboven noemden, plus het voegt een aantal zeer nuttige functies toe, zoals herbruikbaarheid van threads en taakwachtrijen.

Met name de herbruikbaarheid van threads is erg belangrijk: in een grootschalige toepassing zorgt het toewijzen en ongedaan maken van de toewijzing van veel thread-objecten voor een aanzienlijke overhead voor geheugenbeheer.

Met werkdraden minimaliseren we de overhead veroorzaakt door het maken van draden.

Om de zwembadconfiguratie te vergemakkelijken, ExecutorService wordt geleverd met een eenvoudige constructor en enkele aanpassingsopties, zoals het type wachtrij, het minimum en maximum aantal threads en hun naamgevingsconventie.

Voor meer informatie over het ExecutorService, Lees alstublieft onze Gids voor de Java ExecutorService.

4. Een taak beginnen met uitvoerders

Dankzij dit krachtige raamwerk kunnen we onze mentaliteit veranderen van het starten van threads naar het indienen van taken.

Laten we eens kijken hoe we een asynchrone taak kunnen indienen bij onze uitvoerder:

ExecutorService executor = Executors.newFixedThreadPool (10); ... uitvoerder.submit (() -> {nieuwe taak ();});

Er zijn twee methoden die we kunnen gebruiken: uitvoeren, die niets retourneert, en indienen, wat een Toekomst het inkapselen van het resultaat van de berekening.

Voor meer informatie over Futures, lees onze Gids voor java.util.concurrent.Future.

5. Een taak beginnen met CompletableFutures

Om het eindresultaat op te halen uit een Toekomst object kunnen we de krijgen methode beschikbaar in het object, maar dit zou de bovenliggende thread blokkeren tot het einde van de berekening.

Als alternatief kunnen we het blok vermijden door meer logica aan onze taak toe te voegen, maar we moeten de complexiteit van onze code vergroten.

Java 1.8 introduceerde een nieuw framework bovenop het Toekomst construct om beter te werken met het resultaat van de berekening: het CompletableFuture.

CompletableFuture werktuigen CompletableStage, die een uitgebreide selectie methoden toevoegt om callbacks toe te voegen en al het loodgieterswerk te vermijden dat nodig is om bewerkingen uit te voeren op het resultaat nadat het gereed is.

De implementatie om een ​​taak in te dienen is een stuk eenvoudiger:

CompletableFuture.supplyAsync (() -> "Hallo");

supplyAsync duurt een Leverancier met de code die we asynchroon willen uitvoeren - in ons geval de lambda-parameter.

De taak wordt nu impliciet ingediend bij het ForkJoinPool.commonPool (), of we kunnen de Uitvoerder we prefereren als een tweede parameter.

Om er meer over te weten Toekomstige, Lees onze Guide To CompletableFuture.

6. Uitgestelde of periodieke taken uitvoeren

Als we met complexe webapplicaties werken, moeten we mogelijk taken op specifieke tijden uitvoeren, misschien regelmatig.

Java heeft weinig tools die ons kunnen helpen om vertraagde of terugkerende bewerkingen uit te voeren:

  • java.util.Timer
  • java.util.concurrent.ScheduledThreadPoolExecutor

6.1. Timer

Timer is een mogelijkheid om taken te plannen voor toekomstige uitvoering in een achtergrondthread.

Taken kunnen worden gepland voor eenmalige uitvoering of voor herhaalde uitvoering met regelmatige tussenpozen.

Laten we eens kijken hoe de code eruitziet als we een taak na een seconde vertraging willen uitvoeren:

TimerTask task = nieuwe TimerTask () {public void run () {System.out.println ("Taak uitgevoerd op:" + nieuwe datum () + "n" + "Thread's naam:" + Thread.currentThread (). GetName ( )); }}; Timer timer = nieuwe timer ("Timer"); lange vertraging = 1000L; timer.schedule (taak, vertraging);

Laten we nu een terugkerend schema toevoegen:

timer.scheduleAtFixedRate (herhaalde taak, vertraging, periode);

Deze keer wordt de taak uitgevoerd na de opgegeven vertraging en wordt deze herhaald nadat de tijdsperiode is verstreken.

Lees onze handleiding voor Java Timer voor meer informatie.

6.2. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor heeft methoden die vergelijkbaar zijn met de Timer klasse:

ScheduledExecutorService executorService = Executors.newScheduledThreadPool (2); ScheduledFuture resultFuture = executorService.schedule (callableTask, 1, TimeUnit.SECONDS);

Om ons voorbeeld te beëindigen, gebruiken we scheduleAtFixedRate () voor terugkerende taken:

ScheduledFuture resultFuture = executorService.scheduleAtFixedRate (runnableTask, 100, 450, TimeUnit.MILLISECONDS);

De bovenstaande code voert een taak uit na een aanvankelijke vertraging van 100 milliseconden en daarna wordt dezelfde taak elke 450 milliseconden uitgevoerd.

Als de processor de verwerking van de taak niet op tijd voor de volgende gebeurtenis kan voltooien, wordt het ScheduledExecutorService wacht totdat de huidige taak is voltooid, voordat de volgende wordt gestart.

Om deze wachttijd te vermijden, kunnen we gebruik maken van scheduleWithFixedDelay (), die, zoals beschreven door de naam, een vertraging met een vaste lengte tussen iteraties van de taak garandeert.

Voor meer details over ScheduledExecutorService, Lees alstublieft onze Gids voor de Java ExecutorService.

6.3. Welke tool is beter?

Als we de bovenstaande voorbeelden uitvoeren, ziet het resultaat van de berekening er hetzelfde uit.

Zo, hoe kiezen we de juiste tool?

Wanneer een raamwerk meerdere keuzes biedt, is het belangrijk om de onderliggende technologie te begrijpen om een ​​weloverwogen beslissing te nemen.

Laten we proberen wat dieper onder de motorkap te duiken.

Timer:

  • biedt geen real-time garanties: het plant taken met behulp van de Object.wait (lang) methode
  • er is een enkele achtergrondthread, dus taken worden opeenvolgend uitgevoerd en een langlopende taak kan anderen vertragen
  • runtime-uitzonderingen gegooid in een TimerTask zou de enige beschikbare thread doden, dus doden Timer

ScheduledThreadPoolExecutor:

  • kan worden geconfigureerd met een willekeurig aantal threads
  • kan profiteren van alle beschikbare CPU-kernen
  • vangt runtime-uitzonderingen op en laat ons ze afhandelen als we dat willen (door afterExecute methode van ThreadPoolExecutor)
  • annuleert de taak die de uitzondering veroorzaakte, terwijl anderen door konden gaan met uitvoeren
  • vertrouwt op het OS-planningssysteem om tijdzones, vertragingen, zonnetijd enz. bij te houden.
  • biedt een samenwerkings-API als we coördinatie tussen meerdere taken nodig hebben, zoals wachten op de voltooiing van alle ingediende taken
  • biedt een betere API voor het beheer van de levenscyclus van de draad

De keuze is nu duidelijk, toch?

7. Verschil tussen Toekomst en Geplande toekomst

In onze codevoorbeelden kunnen we dat zien ScheduledThreadPoolExecutor geeft een specifiek type terug Toekomst: Geplande toekomst.

Geplande toekomst breidt beide uit Toekomst en Vertraagd interfaces, waardoor de aanvullende methode wordt geërfd getDelay dat geeft de resterende vertraging terug die is gekoppeld aan de huidige taak. Het is verlengd met RunnableScheduledFuture die een methode toevoegt om te controleren of de taak periodiek is.

ScheduledThreadPoolExecutor implementeert al deze constructies via de innerlijke klasse ScheduledFutureTask en gebruikt ze om de levenscyclus van de taak te controleren.

8. Conclusies

In deze tutorial hebben we geëxperimenteerd met de verschillende beschikbare frameworks om threads te starten en taken parallel uit te voeren.

Vervolgens gingen we dieper in op de verschillen tussen Timer en ScheduledThreadPoolExecutor.

De broncode voor het artikel is beschikbaar op GitHub.