Izvršitelji newCachedThreadPool () u odnosu na newFixedThreadPool ()

1. Pregled

Što se tiče implementacija spremišta niti, Java standardna knjižnica nudi mnoštvo mogućnosti za odabir. Fiksni i predmemorirani spremišta niti prilično su sveprisutni među tim implementacijama.

U ovom uputstvu vidjet ćemo kako spremišta niti rade ispod napa, a zatim ćemo usporediti ove implementacije i njihove slučajeve upotrebe.

2. Predmemorirani bazen niti

Pogledajmo kako Java stvara predmemorirano spremište niti kad zovemo Izvršitelji.newCachedThreadPool ():

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

Predmemorirana spremišta niti koriste "sinkronu primopredaju" za postavljanje novih zadataka u red. Osnovna ideja sinkronog primopredaje je jednostavna, a opet kontra-intuitivna: Stavku možete staviti u red čekanja samo i samo ako druga nit istovremeno uzima tu stavku. Drugim riječima, the SynchronousQueue ne mogu držati nikakve zadatke.

Pretpostavimo da ulazi novi zadatak. Ako u redu čeka neaktivna nit, tada proizvođač zadatka predaje zadatak toj niti. Inače, budući da je red uvijek pun, izvršitelj stvara novu nit za obradu tog zadatka.

Predmemorirani bazen započinje s nula niti i potencijalno može rasti Cijeli broj.MAX_VALUE niti. Praktično, jedino ograničenje za predmemorirano spremište niti su dostupni resursi sustava.

Da bi se bolje upravljalo resursima sustava, predmemorirana spremišta niti uklonit će niti koje ostaju neaktivne jednu minutu.

2.1. Koristite slučajeve

Konfiguracija predmemoriranog spremišta niti predmemorira niti (otuda i naziv) na kratko vrijeme kako bi ih ponovno upotrijebila za druge zadatke. Kao rezultat toga, najbolje funkcionira kada imamo posla s razumnim brojem kratkotrajnih zadataka.

Ključ je ovdje „razuman“ i „kratkotrajan“. Da pojasnimo ovu točku, procijenimo scenarij u kojem predmemorirani bazeni ne odgovaraju. Ovdje ćemo predati milijun zadataka od kojih svaki traje 100 mikro-sekundi:

Poziv koji se može pozvati = () -> {long oneHundredMicroSeconds = 100_000; odavno počeoAt = System.nanoTime (); while (System.nanoTime () - startedAt task) .collect (toList ()); var rezultat = cachedPool.invokeAll (zadaci);

Ovo će stvoriti puno niti koje prevode na nerazumno korištenje memorije, i još gore, puno prekidača konteksta CPU-a. Obje ove anomalije znatno bi naštetile ukupnoj izvedbi.

Stoga bismo trebali izbjegavati ovo spremište niti kad je vrijeme izvršavanja nepredvidljivo, poput IO-vezanih zadataka.

3. Bazen s fiksnim nitima

Pogledajmo kako bazeni s fiksnim nitima rade ispod haube:

javna statička ExecutorService newFixedThreadPool (int nThreads) {return new ThreadPoolExecutor (nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue ()); }

Za razliku od predmemoriranog spremišta niti, ovaj koristi neograničeni red s fiksnim brojem niti koje ističu. Stoga, umjesto sve većeg broja niti, fiksno spremište niti pokušava izvršiti dolazne zadatke s fiksnom količinom niti. Kada su sve niti zauzete, tada će izvršitelj postaviti nove zadatke u red čekanja. Na taj način imamo veću kontrolu nad potrošnjom resursa našeg programa.

Kao rezultat toga, fiksna spremišta niti prikladnija su za zadatke s nepredvidljivim vremenima izvršenja.

4. Nesretne sličnosti

Do sada smo samo nabrojali razlike između predmemoriranih i fiksnih spremišta niti.

Sve te razlike na stranu, obje ih koriste AbortPolicy kao njihova politika zasićenja. Stoga očekujemo da će ti izvršitelji izuzeti kad više ne mogu prihvatiti, pa čak ni u redovima ni jednog zadatka.

Pogledajmo što će se dogoditi u stvarnom svijetu.

Predmemorirana spremišta niti nastavit će stvarati sve više i više niti u ekstremnim okolnostima, tako da, praktički, nikada neće doseći točku zasićenja. Slično tome, fiksna spremišta niti nastavit će dodavati sve više i više zadataka u svoj red čekanja. Stoga fiksni bazeni također nikada neće doseći točku zasićenja.

Kako oba spremišta neće biti zasićena, kad je opterećenje izuzetno veliko, potrošit će puno memorije za stvaranje niti ili zadatke u redovima. Dodajući uvredu ozljedi, predmemorirani skupovi niti također će izazvati puno preklopnika konteksta.

Svejedno, do imate veću kontrolu nad potrošnjom resursa, toplo se preporučuje izrada prilagođenog ThreadPoolExeecuter:

var boundedQueue = novi ArrayBlockingQueue (1000); novi ThreadPoolExeecuter (10, 20, 60, SECONDS, boundedQueue, novi AbortPolicy ()); 

Ovdje naše spremište niti može imati do 20 niti i može staviti u red samo do 1000 zadataka. Također, kad više ne može prihvatiti opterećenje, jednostavno će izbaciti iznimku.

5. Zaključak

U ovom smo vodiču zavirili u izvorni kod JDK kako bismo vidjeli koliko se razlikuje Izvršitelj s raditi ispod haube. Zatim smo usporedili fiksni i predmemorirani spremišta niti i njihove slučajeve upotrebe.

Na kraju smo pokušali riješiti izvan-kontrolu potrošnje resursa tih spremišta pomoću prilagođenih spremišta niti.