Oversikt over java.util.concurrent

1. Oversikt

De java.util.concurrent pakken gir verktøy for å lage samtidige applikasjoner.

I denne artikkelen vil vi gjøre en oversikt over hele pakken.

2. Hovedkomponenter

De java.util.concurrent inneholder altfor mange funksjoner å diskutere i en enkelt oppskrift. I denne artikkelen vil vi hovedsakelig fokusere på noen av de mest nyttige verktøyene fra denne pakken som:

  • Leder
  • ExecutorService
  • PlanlagtExecutorService
  • Framtid
  • CountDownLatch
  • CyclicBarrier
  • Semafor
  • Trådfabrikk
  • BlockingQueue
  • DelayQueue
  • Låser
  • Phaser

Du kan også finne mange dedikerte artikler til individuelle klasser her.

2.1. Leder

Leder er et grensesnitt som representerer et objekt som utfører gitt oppgaver.

Det avhenger av den spesifikke implementeringen (hvor innkallingen startes) om oppgaven skal kjøres på en ny eller nåværende tråd. Derfor, ved å bruke dette grensesnittet, kan vi koble fra oppgavens gjennomføringsflyt fra den faktiske oppgavemekanismen.

Et poeng å merke seg her er at Leder krever ikke strengt at oppgavens utførelse skal være asynkron. I det enkleste tilfellet kan en eksekutor påberope seg den innsendte oppgaven umiddelbart i påkallingstråden.

Vi må opprette en påkaller for å opprette eksekutorinstansen:

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

Nå kan vi bruke denne innkalleren til å utføre oppgaven.

public void execute () {Executor executor = new Invoker (); executor.execute (() -> {// oppgave som skal utføres}); }

Merk at her er at hvis eksekutøren ikke kan akseptere oppgaven for utførelse, vil den kaste AvvistExecutionException.

2.2. ExecutorService

ExecutorService er en komplett løsning for asynkron prosessering. Den administrerer en minnekø og planlegger innsendte oppgaver basert på trådtilgjengelighet.

Å bruke ExecutorService, vi trenger å lage en Kjørbar klasse.

offentlig klasse Oppgave implementerer Runnable {@Override public void run () {// oppgavedetaljer}}

Nå kan vi lage ExecutorService forekomst og tilordne denne oppgaven. På tidspunktet for opprettelsen må vi spesifisere trådbassengstørrelsen.

ExecutorService executor = Executors.newFixedThreadPool (10);

Hvis vi ønsker å lage en tråd med én tråd ExecutorService for eksempel kan vi bruke newSingleThreadExecutor (ThreadFactory threadFactory) for å opprette forekomsten.

Når utføreren er opprettet, kan vi bruke den til å sende oppgaven.

public void execute () {executor.submit (new Task ()); }

Vi kan også lage Kjørbar forekomst mens du sender oppgaven.

executor.submit (() -> {new Task ();});

Den leveres også med to avslutningsmetoder utenom boksen. Den første er skru av(); den venter til alle innsendte oppgaver er ferdig utført. Den andre metoden er shutdownNow () which avslutter umiddelbart alle pågående / utførende oppgaver.

Det er også en annen metode awaitTermination (lang tidsavbrudd, TimeUnit-enhet) som kraftig blokkerer til alle oppgaver har fullført kjøring etter at en utkoblingshendelse ble utløst eller utførelse-timeout skjedde, eller selve kjøringstråden blir avbrutt,

prøv {executor.awaitTermination (20l, TimeUnit.NANOSECONDS); } fange (InterruptedException e) {e.printStackTrace (); }

2.3. PlanlagtExecutorService

PlanlagtExecutorService er et lignende grensesnitt som ExecutorService, men det kan utføre oppgaver med jevne mellomrom.

Executor og ExecutorServiceMetodene er planlagt på stedet uten å innføre kunstig forsinkelse. Null eller noen negativ verdi betyr at forespørselen må utføres umiddelbart.

Vi kan bruke begge deler Kjørbar og Kan kalles grensesnitt for å definere oppgaven.

public void execute () {ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor (); Fremtidig fremtid = executorService.schedule (() -> {// ... return "Hello world";}, 1, TimeUnit.SECONDS); ScheduledFuture scheduledFuture = executorService.schedule (() -> {// ...}, 1, TimeUnit.SECONDS); executorService.shutdown (); }

PlanlagtExecutorService kan også planlegge oppgaven etter noen gitt fast forsinkelse:

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

Her, den scheduleAtFixedRate (Kommando som kan kjøres, lang initialDelay, lang periode, TimeUnit-enhet) metoden oppretter og utfører en periodisk handling som påkalles først etter den angitte innledende forsinkelsen, og deretter med den gitte perioden til tjenesteforekomsten stenges.

De scheduleWithFixedDelay (Kommando som kan kjøres, lang initialDelay, lang forsinkelse, TimeUnit-enhet) metoden oppretter og utfører en periodisk handling som påberopes først etter den angitte innledende forsinkelsen, og gjentatte ganger med den gitte forsinkelsen mellom avslutningen av den utførende og påkallingen av den neste.

2.4. Framtid

Framtid brukes til å representere resultatet av en asynkron operasjon. Den leveres med metoder for å sjekke om den asynkrone operasjonen er fullført eller ikke, få det beregnede resultatet, etc.

Hva mer, den avbryt (boolsk mayInterruptIfRunning) API avbryter operasjonen og frigjør kjøringstråden. Hvis verdien av mayInterruptIfRunning er sant, vil tråden som utfører oppgaven bli avsluttet umiddelbart.

Ellers får pågående oppgaver fullført.

Vi kan bruke kodebiten nedenfor for å opprette en fremtidig forekomst:

public void invoke () {ExecutorService executorService = Executors.newFixedThreadPool (10); Fremtidig fremtid = executorService.submit (() -> {// ... Thread.sleep (10000l); returner "Hello world";}); }

Vi kan bruke følgende kodebit for å sjekke om det fremtidige resultatet er klart og hente dataene hvis beregningen er gjort:

hvis (future.isDone () &&! future.isCancelled ()) {prøv {str = future.get (); } fange (InterruptedException | ExecutionException e) {e.printStackTrace (); }}

Vi kan også spesifisere en tidsavbrudd for en gitt operasjon. Hvis oppgaven tar mer enn denne gangen, a TimeoutException blir kastet:

prøv {future.get (10, TimeUnit.SECONDS); } fangst (InterruptedException | ExecutionException | TimeoutException e) {e.printStackTrace (); }

2.5. CountDownLatch

CountDownLatch (introdusert i JDK 5) er en verktøyklasse som blokkerer et sett med tråder til noen operasjoner er fullført.

EN CountDownLatch initialiseres med en teller (heltal type); denne telleren reduseres når de avhengige trådene fullfører kjøringen. Men når telleren når null, blir andre tråder frigitt.

Du kan lære mer om CountDownLatch her.

2.6. CyclicBarrier

CyclicBarrier fungerer nesten det samme som CountDownLatch bortsett fra at vi kan bruke den på nytt. I motsetning til CountDownLatch, lar det flere tråder vente på hverandre ved hjelp av avvente() metoden (kjent som barriereforhold) før den påkaller den siste oppgaven.

Vi må lage en Kjørbar oppgaveinstans for å starte barrierebetingelsen:

offentlig klasse Oppgaveutførelser Runnable {private CyclicBarrier-barriere; offentlig oppgave (CyclicBarrier barrier) {this.barrier = barrier; } @ Override public void run () {prøv {LOG.info (Thread.currentThread (). GetName () + "venter"); barrier.await (); LOG.info (Thread.currentThread (). GetName () + "er utgitt"); } fangst (InterruptedException | BrokenBarrierException e) {e.printStackTrace (); }}}

Nå kan vi påkalle noen tråder for å løpe etter barriertilstanden:

public void start () {CyclicBarrier cyclicBarrier = new CyclicBarrier (3, () -> {// ... LOG.info ("Alle tidligere oppgaver er fullført");}); Tråd t1 = ny tråd (ny oppgave (syklisk barriere), "T1"); Tråd t2 = ny tråd (ny oppgave (cyclicBarrier), "T2"); Tråd t3 = ny tråd (ny oppgave (syklisk barriere), "T3"); hvis (! cyclicBarrier.isBroken ()) {t1.start (); t2.start (); t3.start (); }}

Her, den er ødelagt() metoden sjekker om noen av trådene ble avbrutt i løpet av utførelsestiden. Vi bør alltid utføre denne kontrollen før vi utfører selve prosessen.

2.7. Semafor

De Semafor brukes til å blokkere tilgang på trådnivå til en del av den fysiske eller logiske ressursen. En semafor inneholder et sett med tillatelser; hver gang en tråd prøver å komme inn i den kritiske delen, må den sjekke semaforen om tillatelse er tilgjengelig eller ikke.

Hvis tillatelse ikke er tilgjengelig (via tryAcquire ()), er ikke tråden lov til å hoppe inn i den kritiske delen; men hvis tillatelsen er tilgjengelig, gis tilgangen, og tillatelsedisken avtar.

Når den utførende tråden frigjør den kritiske delen, øker tillatelsestelleren igjen (gjort av utgivelse() metode).

Vi kan spesifisere et tidsavbrudd for å skaffe tilgang ved å bruke tryAcquire (lang tidsavbrudd, TimeUnit-enhet) metode.

Vi kan også sjekke antall tilgjengelige tillatelser eller antall tråder som venter på å skaffe seg semaforen.

Følgende kodebit kan brukes til å implementere en semafor:

statisk semafor semafor = ny semafor (10); offentlig tomrom utføre () kaster InterruptedException {LOG.info ("Tilgjengelig tillatelse:" + semaphore.availablePermits ()); LOG.info ("Antall tråder som venter på å skaffe seg:" + semaphore.getQueueLength ()); hvis (semaphore.tryAcquire ()) {prøv {// ...} til slutt {semaphore.release (); }}}

Vi kan implementere en Mutex som datastruktur ved hjelp av Semafor. Flere detaljer om dette finner du her.

2.8. Trådfabrikk

Som navnet antyder, Trådfabrikk fungerer som en tråd (ikke-eksisterende) pool som skaper en ny tråd på forespørsel. Det eliminerer behovet for mye koding av kjeleplaten for å implementere effektive trådopprettingsmekanismer.

Vi kan definere en Trådfabrikk:

offentlig klasse BaeldungThreadFactory implementerer ThreadFactory {private int threadId; privat strengnavn; offentlig BaeldungThreadFactory (strengnavn) {threadId = 1; this.name = navn; } @ Override public Tråd newThread (Runnable r) {Thread t = new Thread (r, name + "-Thread_" + threadId); LOG.info ("opprettet ny tråd med id:" + threadId + "og navn:" + t.getName ()); threadId ++; returnere t; }}

Vi kan bruke dette newThread (Runnable r) metode for å lage en ny tråd ved kjøretid:

BaeldungThreadFactory fabrikk = ny BaeldungThreadFactory ("BaeldungThreadFactory"); for (int i = 0; i <10; i ++) {Thread t = factory.newThread (new Task ()); t.start (); }

2.9. BlockingQueue

I asynkron programmering er produsent-forbruker-mønster et av de vanligste integrasjonsmønstrene. De java.util.concurrent pakken kommer med en datastruktur kjent som BlockingQueue - som kan være veldig nyttig i disse asynkroniseringsscenarioene.

Mer informasjon og et arbeidseksempel på dette er tilgjengelig her.

2.10. DelayQueue

DelayQueue er en uendelig størrelse blokkeringskø av elementer der et element bare kan trekkes hvis utløpstiden (kjent som brukerdefinert forsinkelse) er fullført. Derfor er det øverste elementet (hode) vil ha den største mengden forsinkelse, og den blir spurt sist.

Mer informasjon og et arbeidseksempel på dette er tilgjengelig her.

2.11. Låser

Ikke overraskende, Låse er et verktøy for å blokkere andre tråder fra å få tilgang til et bestemt kodesegment, bortsett fra tråden som utfører den for øyeblikket.

Hovedforskjellen mellom en lås og en synkronisert blokk er at synkronisert blokk er fullstendig inneholdt i en metode; Vi kan imidlertid ha Lock APIs lås () og låse opp () i separate metoder.

Mer informasjon og et arbeidseksempel på dette er tilgjengelig her.

2.12. Phaser

Phaser er en mer fleksibel løsning enn CyclicBarrier og CountDownLatch - brukes til å fungere som en gjenbrukbar barriere som det dynamiske antallet tråder må vente på før du fortsetter kjøringen. Vi kan koordinere flere utførelsesfaser ved å gjenbruke a Phaser forekomst for hver programfase.

Mer informasjon og et arbeidseksempel på dette er tilgjengelig her.

3. Konklusjon

I denne oversiktsartikkelen på høyt nivå har vi fokusert på de forskjellige verktøyene som er tilgjengelige java.util.concurrent pakke.

Som alltid er hele kildekoden tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found