Eksekutører newCachedThreadPool () vs newFixedThreadPool ()

1. Oversikt

Når det gjelder trådpoolimplementeringer, gir Java-standardbiblioteket mange alternativer å velge mellom. De faste og hurtigbufrede trådbassengene er ganske allestedsnærværende blant disse implementeringene.

I denne opplæringen skal vi se hvordan trådbassenger fungerer under panseret, og deretter sammenligne disse implementeringene og brukssakene.

2. Bufret trådbasseng

La oss ta en titt på hvordan Java oppretter et hurtigbufret trådbasseng når vi ringer Executors.newCachedThreadPool ():

offentlig statisk ExecutorService newCachedThreadPool () {return new ThreadPoolExecutor (0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue ()); }

Bufrede trådbassenger bruker “synkron overføring” for å stille nye oppgaver i kø. Den grunnleggende ideen om synkron utlevering er enkel og likevel kontraintuitiv: Man kan kø et element i kø hvis og bare hvis en annen tråd tar det elementet samtidig. Med andre ord, de SynchronousQueue kan ikke holde noen oppgaver overhodet.

Anta at en ny oppgave kommer inn. Hvis det er en inaktiv tråd som venter på køen, overleverer oppgaveprodusenten oppgaven til den tråden. Ellers, siden køen alltid er full, oppretter eksekutøren en ny tråd for å håndtere den oppgaven.

Det hurtigbufrede bassenget starter med null tråder og kan potensielt vokse til å ha Heltall.MAX_VALUE tråder. I praksis er den eneste begrensningen for en hurtigbufret trådgruppe de tilgjengelige systemressursene.

For bedre å administrere systemressurser fjerner hurtigbufrede trådbassenger tråder som forblir inaktive i ett minutt.

2.1. Bruk tilfeller

Den hurtigbufrede konfigurasjonen av trådbasseng cacher trådene (derav navnet) i kort tid for å gjenbruke dem til andre oppgaver. Som et resultat fungerer det best når vi har å gjøre med et rimelig antall kortvarige oppgaver.

Nøkkelen her er “rimelig” og “kortvarig”. For å avklare dette punktet, la oss evaluere et scenario der hurtigbufrede bassenger ikke passer bra. Her skal vi sende inn en million oppgaver som hver tar 100 mikrosekunder å fullføre:

Kallbar oppgave = () -> {long oneHundredMicroSeconds = 100_000; lenge startedAt = System.nanoTime (); mens (System.nanoTime () - startedAt task) .collect (toList ()); var resultat = cachedPool.invokeAll (oppgaver);

Dette kommer til å skape mange tråder som oversettes til urimelig minnebruk, og enda verre, mange CPU-kontekstbrytere. Begge disse avvikene ville skade den generelle ytelsen betydelig.

Derfor bør vi unngå denne trådgruppen når utførelsestiden er uforutsigbar, som IO-bundne oppgaver.

3. Fast trådbasseng

La oss se hvordan faste trådbassenger fungerer under panseret:

offentlig statisk ExecutorService newFixedThreadPool (int nThreads) {return new ThreadPoolExecutor (nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue ()); }

I motsetning til den bufrede trådgruppen, bruker denne en ubundet kø med et fast antall tråder som aldri utløper. Derfor, i stedet for et stadig økende antall tråder, prøver den faste trådgruppen å utføre innkommende oppgaver med en fast mengde tråder. Når alle tråder er opptatt, vil eksekutøren stille nye oppgaver i kø. På denne måten har vi mer kontroll over programmets ressursforbruk.

Som et resultat er faste trådbassenger bedre egnet for oppgaver med uforutsigbare utførelsestider.

4. Uheldige likheter

Så langt har vi bare oppregnet forskjellene mellom hurtigbufrede og faste trådbassenger.

Alle disse forskjellene til side, de bruker begge Avbryt politikk som deres metningspolitikk. Derfor forventer vi at disse eksekutørene kaster et unntak når de ikke kan godta og til og med stille flere oppgaver i kø.

La oss se hva som skjer i den virkelige verden.

Bufrede trådbassenger vil fortsette å lage flere og flere tråder under ekstreme omstendigheter, så praktisk talt de vil aldri nå et metningspunkt. Tilsvarende vil faste trådbassenger fortsette å legge til flere og flere oppgaver i køen. Derfor vil de faste bassengene aldri nå et metningspunkt.

Ettersom begge bassengene ikke blir mettede, vil belastningen, når belastningen er usedvanlig høy, forbruke mye minne for å lage tråder eller køoppgaver. Ved å legge fornærmelse mot skaden, vil bufrede trådbassenger også medføre mange prosessorkontektsbrytere.

Uansett, til ha mer kontroll over ressursforbruk, anbefales det sterkt å lage en egendefinert ThreadPoolExecutor:

var boundedQueue = ny ArrayBlockingQueue (1000); ny ThreadPoolExecutor (10, 20, 60, SECONDS, boundedQueue, new AbortPolicy ()); 

Her kan trådpuljen vår ha opptil 20 tråder og kan bare stå i kø opptil 1000 oppgaver. Når den ikke kan akseptere mer belastning, vil den bare kaste et unntak.

5. Konklusjon

I denne opplæringen tok vi en titt på JDK-kildekoden for å se hvor annerledes Leder s arbeid under panseret. Deretter sammenlignet vi de faste og hurtigbufrede trådbassengene og brukssakene deres.

Til slutt prøvde vi å adressere ressursforbruket utenfor kontrollene til disse bassengene med tilpassede trådbassenger.


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