Overzicht van het bestand java.util.concurrent

1. Overzicht

De java.util.concurrent pakket biedt tools voor het creëren van gelijktijdige applicaties.

In dit artikel zullen we een overzicht geven van het hele pakket.

2. Hoofdcomponenten

De java.util.concurrent bevat veel te veel functies om in één artikel te bespreken. In dit artikel zullen we ons voornamelijk concentreren op enkele van de handigste hulpprogramma's uit dit pakket, zoals:

  • Uitvoerder
  • ExecutorService
  • ScheduledExecutorService
  • Toekomst
  • CountDownLatch
  • CyclicBarrier
  • Semafoor
  • ThreadFactory
  • BlockingQueue
  • DelayQueue
  • Sloten
  • Phaser

Je kunt hier ook veel speciale artikelen voor individuele lessen vinden.

2.1. Uitvoerder

Uitvoerder is een interface die een object vertegenwoordigt dat de opgegeven taken uitvoert.

Het hangt af van de specifieke implementatie (van waaruit de aanroep wordt gestart) of de taak moet worden uitgevoerd op een nieuwe of huidige thread. Daarom kunnen we met behulp van deze interface de stroom voor het uitvoeren van taken loskoppelen van het feitelijke mechanisme voor het uitvoeren van taken.

Een punt om hier op te merken is dat Uitvoerder vereist niet strikt dat de taakuitvoering asynchroon is. In het eenvoudigste geval kan een uitvoerder de ingediende taak direct aanroepen in de aanroepende thread.

We moeten een invoker maken om de executor-instantie te maken:

openbare klasse Invoker implementeert Executor {@Override public void execute (Runnable r) {r.run (); }}

Nu kunnen we deze aanroeper gebruiken om de taak uit te voeren.

public void execute () {Executor executor = nieuwe Invoker (); executor.execute (() -> {// uit te voeren taak}); }

Het punt om hier op te merken is dat als de uitvoerder de taak niet kan accepteren voor uitvoering, deze zal gooien RejectedExecutionException.

2.2. ExecutorService

ExecutorService is een complete oplossing voor asynchrone verwerking. Het beheert een wachtrij in het geheugen en plant ingediende taken op basis van de beschikbaarheid van threads.

Gebruiken ExecutorService, we moeten er een maken Runnable klasse.

public class Task implementeert Runnable {@Override public void run () {// taakdetails}}

Nu kunnen we het ExecutorService instantie en wijs deze taak toe. Op het moment van maken moeten we de grootte van de thread-pool specificeren.

ExecutorService executor = Executors.newFixedThreadPool (10);

Als we een single-threaded ExecutorService we kunnen bijvoorbeeld gebruiken newSingleThreadExecutor (ThreadFactory threadFactory) om de instantie te maken.

Zodra de uitvoerder is aangemaakt, kunnen we deze gebruiken om de taak in te dienen.

public void execute () {executor.submit (nieuwe taak ()); }

We kunnen ook het Runnable instantie tijdens het indienen van de taak.

executor.submit (() -> {nieuwe taak ();});

Het wordt ook geleverd met twee kant-en-klare beëindigingsmethoden. De eerste is afsluiten(); het wacht totdat alle ingediende taken zijn voltooid. De andere methode is shutdownNow () which beëindigt onmiddellijk alle lopende / uitvoerende taken.

Er is ook een andere methode awaitTermination (lange time-out, TimeUnit-eenheid) die met kracht blokkeert totdat alle taken de uitvoering hebben voltooid nadat een afsluitgebeurtenis is geactiveerd of een time-out is opgetreden, of de uitvoeringsthread zelf is onderbroken,

probeer {executor.awaitTermination (20l, TimeUnit.NANOSECONDS); } catch (InterruptedException e) {e.printStackTrace (); }

2.3. ScheduledExecutorService

ScheduledExecutorService is een vergelijkbare interface als ExecutorService, maar het kan periodiek taken uitvoeren.

Executor en ExecutorService‘S methoden worden ter plaatse gepland zonder kunstmatige vertraging te introduceren. Een nulwaarde of een negatieve waarde betekent dat het verzoek onmiddellijk moet worden uitgevoerd.

We kunnen beide gebruiken Runnable en Oproepbaar interface om de taak te definiëren.

openbare void execute () {ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor (); Future future = executorService.schedule (() -> {// ... return "Hallo wereld";}, 1, TimeUnit.SECONDS); ScheduledFuture geplandFuture = executorService.schedule (() -> {// ...}, 1, TimeUnit.SECONDS); executorService.shutdown (); }

ScheduledExecutorService kan de taak ook plannen na een bepaalde vaste vertraging:

executorService.scheduleAtFixedRate (() -> {// ...}, 1, 10, TimeUnit.SECONDS); executorService.scheduleWithFixedDelay (() -> {// ...}, 1, 10, TimeUnit.SECONDS);

Hier de scheduleAtFixedRate (Runnable-opdracht, lange initialDelay, lange periode, TimeUnit-eenheid) methode maakt en voert een periodieke actie uit die eerst wordt aangeroepen na de opgegeven initiële vertraging en vervolgens met de opgegeven periode totdat de service-instantie wordt afgesloten.

De scheduleWithFixedDelay (Runnable-opdracht, lange initialDelay, lange vertraging, TimeUnit-eenheid) methode creëert en voert een periodieke actie uit die eerst wordt aangeroepen na de voorziene initiële vertraging, en herhaaldelijk met de gegeven vertraging tussen de beëindiging van de uitvoerende en het aanroepen van de volgende.

2.4. Toekomst

Toekomst wordt gebruikt om het resultaat van een asynchrone bewerking weer te geven. Het wordt geleverd met methoden om te controleren of de asynchrone bewerking is voltooid of niet, om het berekende resultaat te krijgen, enz.

Wat meer is, de annuleren (boolean mayInterruptIfRunning) API annuleert de bewerking en geeft de uitvoerende thread vrij. Als de waarde van mayInterruptIfRunning waar is, wordt de thread die de taak uitvoert onmiddellijk beëindigd.

Anders kunnen lopende taken worden voltooid.

We kunnen het onderstaande codefragment gebruiken om een ​​toekomstige instantie te maken:

openbare void invoke () {ExecutorService executorService = Executors.newFixedThreadPool (10); Future future = executorService.submit (() -> {// ... Thread.sleep (10000l); return "Hallo wereld";}); }

We kunnen het volgende codefragment gebruiken om te controleren of het toekomstige resultaat klaar is en de gegevens ophalen als de berekening is voltooid:

if (future.isDone () &&! future.isCancelled ()) {probeer {str = future.get (); } catch (InterruptedException | ExecutionException e) {e.printStackTrace (); }}

We kunnen ook een time-out specificeren voor een bepaalde bewerking. Als de taak langer duurt dan deze tijd, a TimeoutException wordt gegooid:

probeer {future.get (10, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) {e.printStackTrace (); }

2.5. CountDownLatch

CountDownLatch (geïntroduceerd in JDK 5) is een hulpprogramma-klasse die een reeks threads blokkeert totdat een bewerking is voltooid.

EEN CountDownLatch wordt geïnitialiseerd met een counter (geheel getal type); deze teller neemt af naarmate de afhankelijke threads de uitvoering voltooien. Maar zodra de teller nul bereikt, worden andere threads vrijgegeven.

U kunt er meer over leren CountDownLatch hier.

2.6. CyclicBarrier

CyclicBarrier werkt bijna hetzelfde als CountDownLatch behalve dat we het kunnen hergebruiken. in tegenstelling tot CountDownLatch, kunnen meerdere threads op elkaar wachten met behulp van wachten() methode (bekend als barrièreconditie) voordat de laatste taak wordt aangeroepen.

We moeten een Runnable taakinstantie om de barrièreconditie te starten:

openbare klasse Taak implementeert Runnable {private CyclicBarrier-barrière; openbare taak (CyclicBarrier-barrière) {this.barrier = barrier; } @Override public void run () {probeer {LOG.info (Thread.currentThread (). GetName () + "wacht"); barrier.await (); LOG.info (Thread.currentThread (). GetName () + "is vrijgegeven"); } catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace (); }}}

Nu kunnen we enkele threads aanroepen om te racen voor de barrièreconditie:

public void start () {CyclicBarrier cyclicBarrier = new CyclicBarrier (3, () -> {// ... LOG.info ("Alle voorgaande taken zijn voltooid");}); Thread t1 = nieuwe Thread (nieuwe taak (cyclicBarrier), "T1"); Thread t2 = nieuwe Thread (nieuwe taak (cyclicBarrier), "T2"); Thread t3 = nieuwe Thread (nieuwe taak (cyclicBarrier), "T3"); if (! cyclicBarrier.isBroken ()) {t1.start (); t2.start (); t3.start (); }}

Hier de is kapot() methode controleert of een van de threads is onderbroken tijdens de uitvoeringstijd. We moeten deze controle altijd uitvoeren voordat we het eigenlijke proces uitvoeren.

2.7. Semafoor

De Semafoor wordt gebruikt voor het blokkeren van toegang op threadniveau tot een deel van de fysieke of logische bron. Een seinpaal bevat een reeks vergunningen; telkens wanneer een thread probeert de kritieke sectie binnen te gaan, moet deze de semafoor controleren of er een vergunning beschikbaar is of niet.

Als er geen vergunning beschikbaar is (via tryAcquire ()), mag de thread niet in de kritieke sectie springen; als de vergunning echter beschikbaar is, wordt de toegang verleend en neemt de vergunningsteller af.

Zodra de uitvoerende thread de kritieke sectie vrijgeeft, wordt opnieuw de vergunningenteller verhoogd (gedaan door vrijlating() methode).

We kunnen een time-out specificeren voor het verkrijgen van toegang door de tryAcquire (lange time-out, TimeUnit-eenheid) methode.

We kunnen ook het aantal beschikbare vergunningen controleren of het aantal threads dat wacht op het verkrijgen van de semafoor.

Het volgende codefragment kan worden gebruikt om een ​​semafoor te implementeren:

statische semafoor semafoor = nieuwe semafoor (10); public void execute () gooit InterruptedException {LOG.info ("Beschikbare vergunning:" + semaphore.availablePermits ()); LOG.info ("Aantal threads dat wacht op verwerving:" + semaphore.getQueueLength ()); if (semaphore.tryAcquire ()) {probeer {// ...} eindelijk {semaphore.release (); }}}

We kunnen een Mutex zoals datastructuur met Semafoor. Meer informatie hierover vindt u hier.

2.8. ThreadFactory

Zoals de naam al doet vermoeden, ThreadFactory fungeert als een thread (niet-bestaande) pool die op aanvraag een nieuwe thread maakt. Het elimineert de noodzaak van veel boilerplate-codering voor het implementeren van efficiënte mechanismen voor het maken van threads.

We kunnen een ThreadFactory:

openbare klasse BaeldungThreadFactory implementeert ThreadFactory {private int threadId; private String naam; openbare BaeldungThreadFactory (String naam) {threadId = 1; this.name = naam; } @Override openbare thread newThread (Runnable r) {Thread t = nieuwe thread (r, naam + "-Thread_" + threadId); LOG.info ("heeft een nieuwe thread gemaakt met id:" + threadId + "en naam:" + t.getName ()); threadId ++; terug t; }}

We kunnen dit gebruiken newThread (Runnable r) methode om tijdens runtime een nieuwe thread te maken:

BaeldungThreadFactory factory = nieuwe BaeldungThreadFactory ("BaeldungThreadFactory"); voor (int i = 0; i <10; i ++) {Thread t = factory.newThread (nieuwe taak ()); t.start (); }

2.9. BlockingQueue

Bij asynchroon programmeren is een van de meest voorkomende integratiepatronen het patroon van producent en consument. De java.util.concurrent pakket wordt geleverd met een datastructuur bekend als BlockingQueue - wat erg handig kan zijn in deze asynchrone scenario's.

Meer informatie en een werkvoorbeeld hierover is hier beschikbaar.

2.10. DelayQueue

DelayQueue is een oneindige blokkeringswachtrij van elementen waar een element alleen kan worden opgehaald als de vervaltijd (bekend als door de gebruiker gedefinieerde vertraging) is voltooid. Vandaar dat het bovenste element (hoofd) heeft de meeste vertraging en wordt als laatste ondervraagd.

Meer informatie en een werkvoorbeeld hierover is hier beschikbaar.

2.11. Sloten

Niet verrassend, Slot is een hulpprogramma om te voorkomen dat andere threads toegang krijgen tot een bepaald codesegment, behalve de thread die het momenteel uitvoert.

Het belangrijkste verschil tussen een slot en een gesynchroniseerd blok is dat een gesynchroniseerd blok volledig is vervat in een methode; We kunnen echter de lock () en unlock () -bewerking van de Lock API op verschillende manieren uitvoeren.

Meer informatie en een werkvoorbeeld hierover is hier beschikbaar.

2.12. Phaser

Phaser is een meer flexibele oplossing dan CyclicBarrier en CountDownLatch - fungeerde vroeger als een herbruikbare barrière waarop het dynamische aantal threads moet wachten voordat de uitvoering wordt voortgezet. We kunnen meerdere uitvoeringsfasen coördineren, waarbij we een Phaser bijvoorbeeld voor elke programmafase.

Meer informatie en een werkvoorbeeld hierover is hier beschikbaar.

3. Conclusie

In dit overzichtsartikel op hoog niveau hebben we ons gericht op de verschillende beschikbare hulpprogramma's van java.util.concurrent pakket.

Zoals altijd is de volledige broncode beschikbaar op GitHub.