Uitvoerders newCachedThreadPool () versus newFixedThreadPool ()

1. Overzicht

Als het gaat om implementaties van threadpools, biedt de standaard Java-bibliotheek tal van opties om uit te kiezen. De vaste en gecachte threadpools zijn vrij alomtegenwoordig onder die implementaties.

In deze zelfstudie gaan we zien hoe threadpools onder de motorkap werken en vergelijken we deze implementaties en hun use-cases.

2. Discussiegroep in cache

Laten we eens kijken hoe Java een threadpool in de cache maakt wanneer we bellen Executors.newCachedThreadPool ():

openbare statische ExecutorService newCachedThreadPool () {retourneer nieuwe ThreadPoolExecutor (0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, nieuwe SynchronousQueue ()); }

Threadpools in de cache gebruiken "synchrone overdracht" om nieuwe taken in de wachtrij te plaatsen. Het basisidee van synchrone overdracht is eenvoudig en toch contra-intuïtief: men kan een item in de wachtrij plaatsen als en alleen als een andere thread dat item tegelijkertijd pakt. Met andere woorden, de Synchrone wachtrij kan geen enkele taak hebben.

Stel dat er een nieuwe taak binnenkomt. Als er een inactieve thread in de wachtrij staat, draagt ​​de taakproducent de taak over aan die thread. Anders, aangezien de wachtrij altijd vol is, maakt de uitvoerder een nieuwe thread om die taak af te handelen.

De pool in de cache begint met nul threads en kan mogelijk uitgroeien tot Geheel getal.MAX_VALUE draden. Praktisch gezien is de enige beperking voor een threadpool in de cache de beschikbare systeembronnen.

Om systeembronnen beter te beheren, zullen threadpools in de cache threads verwijderen die een minuut inactief blijven.

2.1. Gebruik cases

De configuratie van de threadpool in de cache slaat de threads (vandaar de naam) voor een korte tijd op in de cache om ze opnieuw te gebruiken voor andere taken. Het resultaat is dat het het beste werkt als we te maken hebben met een redelijk aantal kortstondige taken.

De sleutel hier is "redelijk" en "van korte duur". Laten we, om dit punt te verduidelijken, een scenario evalueren waarin pools in de cache niet goed passen. Hier gaan we een miljoen taken indienen die elk 100 microseconden nodig hebben om te voltooien:

Oproepbare taak = () -> {lange oneHundredMicroSeconds = 100_000; long startsAt = System.nanoTime (); while (System.nanoTime () - startedAt task) .collect (toList ()); var resultaat = cachedPool.invokeAll (taken);

Dit gaat veel threads creëren die zich vertalen in onredelijk geheugengebruik, en erger nog, veel CPU-contextwisselingen. Beide afwijkingen zouden de algehele prestaties aanzienlijk schaden.

Daarom moeten we deze threadpool vermijden wanneer de uitvoeringstijd onvoorspelbaar is, zoals IO-gebonden taken.

3. Pool met vaste draden

Laten we eens kijken hoe pools met vaste draden werken onder de motorkap:

openbare statische ExecutorService newFixedThreadPool (int nThreads) {retourneer nieuwe ThreadPoolExecutor (nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, nieuwe LinkedBlockingQueue ()); }

In tegenstelling tot de threadpool in de cache, gebruikt deze een onbegrensde wachtrij met een vast aantal nooit aflopende threads. Daarom probeert de pool met vaste threads in plaats van een steeds groter aantal threads inkomende taken uit te voeren met een vast aantal threads. Als alle threads bezet zijn, zal de uitvoerder nieuwe taken in de wachtrij plaatsen. Op deze manier hebben we meer controle over het bronnenverbruik van ons programma.

Als gevolg hiervan zijn pools met vaste threads beter geschikt voor taken met onvoorspelbare uitvoeringstijden.

4. Ongelukkige overeenkomsten

Tot nu toe hebben we alleen de verschillen opgesomd tussen pools in cache en fixed thread.

Afgezien van al die verschillen, zijn ze allebei nuttig Beleid afbreken als hun verzadigingsbeleid. Daarom verwachten we dat deze uitvoerders een uitzondering genereren wanneer ze geen taken meer kunnen accepteren en zelfs in de wachtrij plaatsen.

Laten we eens kijken wat er in de echte wereld gebeurt.

Threadpools in de cache zullen in extreme omstandigheden steeds meer threads blijven maken, dus praktisch gezien, ze zullen nooit een verzadigingspunt bereiken. Op dezelfde manier zullen pools met vaste discussies steeds meer taken in hun wachtrij blijven toevoegen. Daarom zullen de vaste baden ook nooit een verzadigingspunt bereiken.

Aangezien beide pools niet verzadigd zullen zijn, zullen ze, wanneer de belasting uitzonderlijk hoog is, veel geheugen in beslag nemen voor het maken van threads of wachtrijtaken. Om de blessure nog erger te maken, zullen threadpools in de cache ook veel processorcontextwisselingen met zich meebrengen.

Hoe dan ook, naar meer controle hebben over het resourceverbruik, wordt het ten zeerste aanbevolen om een ​​aangepast ThreadPoolExecutor:

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

Hier kan onze threadpool maximaal 20 threads hebben en maximaal 1000 taken in de wachtrij plaatsen. Als het geen belasting meer kan accepteren, genereert het ook gewoon een uitzondering.

5. Conclusie

In deze tutorial hebben we een kijkje genomen in de JDK-broncode om te zien hoe verschillend Uitvoerder s werk onder de motorkap. Vervolgens hebben we de threadpools met vaste en in de cache opgeslagen en hun use-cases vergeleken.

Uiteindelijk hebben we geprobeerd het onbeheerste resourceverbruik van die pools aan te pakken met aangepaste threadpools.