Eksekutører newCachedThreadPool () vs newFixedThreadPool ()

1. Oversigt

Når det kommer til implementering af trådpuljer, giver Java-standardbiblioteket masser af muligheder at vælge imellem. De faste og cachelagrede trådpuljer er ret allestedsnærværende blandt disse implementeringer.

I denne vejledning skal vi se, hvordan trådpuljer fungerer under emhætten og derefter sammenligne disse implementeringer og deres brugssager.

2. Cache tråd pool

Lad os se på, hvordan Java opretter en cache-trådpul, når vi ringer Executors.newCachedThreadPool ():

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

Cachede trådpuljer bruger “synkron overdragelse” til at sætte nye opgaver i kø. Grundideen med synkron udlevering er enkel og alligevel kontraintuitiv: Man kan kø et element i kø, hvis og kun hvis en anden tråd tager det element på samme tid. Med andre ord, det SynchronousQueue kan ikke holde nogen opgaver overhovedet.

Antag, at en ny opgave kommer ind. Hvis der er en ledig tråd, der venter i køen, afleverer opgaveproducenten opgaven til den tråd. Ellers, da køen altid er fuld, opretter eksekutøren en ny tråd til at håndtere denne opgave.

Den cachelagrede pool starter med nul tråde og kan potentielt vokse til at have Heltal.MAX_VALUE tråde. Praktisk set er den eneste begrænsning for en cachelagret trådpulje de tilgængelige systemressourcer.

For bedre at styre systemressourcer fjernes cachelagrede trådpuljer tråde, der forbliver inaktive i et minut.

2.1. Brug sager

Den cachelagrede trådpoolkonfiguration cacher trådene (deraf navnet) i en kort periode for at genbruge dem til andre opgaver. Som et resultat fungerer det bedst, når vi har at gøre med et rimeligt antal kortvarige opgaver.

Nøglen her er “rimelig” og “kortvarig”. For at afklare dette punkt, lad os evaluere et scenario, hvor cachelagrede puljer ikke passer godt. Her skal vi indsende en million opgaver, der hver tager 100 mikrosekunder at afslutte:

Opkaldelig opgave = () -> {lang oneHundredMicroSeconds = 100_000; længe startetAt = System.nanoTime (); mens (System.nanoTime () - startedAt task) .collect (toList ()); var resultat = cachedPool.invokeAll (opgaver);

Dette vil skabe mange tråde, der oversættes til urimelig hukommelsesforbrug, og endnu værre, masser af CPU-kontekstskiftere. Begge disse anomalier ville skade den samlede præstation betydeligt.

Derfor bør vi undgå denne trådpulje, når udførelsestiden er uforudsigelig, ligesom IO-bundne opgaver.

3. Fast trådpool

Lad os se, hvordan faste trådpuljer fungerer under emhætten:

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

I modsætning til den cachelagrede trådpulje bruger denne en ubegrænset kø med et fast antal uendelige tråde. Derfor forsøger den faste trådpulje i stedet for et stadigt stigende antal tråde at udføre indgående opgaver med en fast mængde tråde. Når alle tråde er optaget, vil eksekutøren sætte nye opgaver i kø. På denne måde har vi mere kontrol over vores programs ressourceforbrug.

Som et resultat er faste trådpooler bedre egnet til opgaver med uforudsigelige udførelsestider.

4. Uheldige ligheder

Indtil videre har vi kun opregnet forskellene mellem cachelagrede og faste trådpuljer.

Alle disse forskelle til side, de er begge brug Afbryd politik som deres mætningspolitik. Derfor forventer vi, at disse eksekutører kaster en undtagelse, når de ikke kan acceptere og endda stille flere opgaver i kø.

Lad os se, hvad der sker i den virkelige verden.

Cachede trådpuljer vil fortsat skabe flere og flere tråde under ekstreme omstændigheder, så praktisk taget de når aldrig et mætningspunkt. Tilsvarende vil faste trådpuljer fortsætte med at tilføje flere og flere opgaver i deres kø. Derfor når de faste puljer heller aldrig et mætningspunkt.

Da begge puljer ikke bliver mættede, når belastningen er usædvanlig høj, vil de forbruge en masse hukommelse til oprettelse af tråde eller køopgaver. Hvis du lægger fornærmelse mod skaden, vil cachelagrede trådpuljer også pådrage sig mange processorkontekstomskiftere.

Alligevel til har mere kontrol over ressourceforbrug, anbefales det stærkt at oprette en brugerdefineret ThreadPoolExecutor:

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

Her kan vores trådpulje have op til 20 tråde og kan kun kø i op til 1000 opgaver. Også når den ikke kan acceptere mere belastning, kaster den simpelthen en undtagelse.

5. Konklusion

I denne vejledning kiggede vi ind i JDK-kildekoden for at se, hvor anderledes Eksekutor s arbejde under emhætten. Derefter sammenlignede vi de faste og cachelagrede trådpuljer og deres brugssager.

I sidste ende forsøgte vi at adressere ressourceforbruget uden for kontrol af disse puljer med tilpassede trådpuljer.