Kako pokrenuti nit u Javi

1. Uvod

U ovom uputstvu istražit ćemo različite načine za pokretanje niti i izvršavanje paralelnih zadataka.

Ovo je vrlo korisno, posebno kada se radi o dugim ili ponavljajućim operacijama koje se ne mogu izvoditi na glavnoj niti, ili gdje se interakcija korisničkog sučelja ne može staviti na čekanje dok se čekaju rezultati operacije.

Da biste saznali više o pojedinostima niti, definitivno pročitajte naš vodič o životnom ciklusu niti u Javi.

2. Osnove vođenja niti

Pomoću datoteke. Lako možemo napisati logiku koja se izvodi u paralelnoj niti Nit okvir.

Pokušajmo s osnovnim primjerom, proširujući Nit razred:

javna klasa NewThread proširuje Thread {public void run () {long startTime = System.currentTimeMillis (); int i = 0; while (true) {System.out.println (this.getName () + ": Nova nit je pokrenuta ..." + i ++); probajte {// Pričekajte jednu sekundu da se ne ispiše prebrzo Thread.sleep (1000); } catch (InterruptedException e) {e.printStackTrace (); } ...}}}

A sada napišemo drugu klasu za inicijalizaciju i pokretanje naše niti:

javna klasa SingleThreadExample {public static void main (String [] args) {NewThread t = new NewThread (); t.start (); }}

Trebali bismo nazvati početak() metoda na nitima u NOVI država (ekvivalent nezapočetom). Inače, Java će baciti primjerak IllegalThreadStateException iznimka.

Sada pretpostavimo da trebamo pokrenuti više niti:

javna klasa MultipleThreadsExample {public static void main (String [] args) {NewThread t1 = new NewThread (); t1.setName ("MyThread-1"); NewThread t2 = novi NewThread (); t2.setName ("MyThread-2"); t1.start (); t2.start (); }}

Naš kod i dalje izgleda prilično jednostavno i vrlo je sličan primjerima koje možemo pronaći na mreži.

Naravno, ovo je daleko od proizvodno spremnog koda, gdje je od ključne važnosti pravilno upravljati resursima, kako bi se izbjeglo previše prebacivanja konteksta ili previše upotrebe memorije.

Dakle, da bismo bili spremni za proizvodnju, sada moramo napisati dodatni obrazac nositi se sa:

  • dosljedno stvaranje novih niti
  • broj istodobnih živih niti
  • uklanjanje niti: vrlo važno za demonske niti kako bi se izbjeglo curenje

Ako želimo, možemo napisati vlastiti kôd za sve ove scenarije slučaja, pa čak i neke druge, ali zašto bismo ponovno otkrili kotač?

3. The ExecutorService Okvir

The ExecutorService implementira obrazac dizajna Thread Pool (koji se naziva i repliciranim modelom radnika ili radničke posade) i brine o upravljanju nitima koje smo gore spomenuli, a dodaje i neke vrlo korisne značajke poput ponovne upotrebe niti i redova zadataka.

Osobito je ponovna upotreba niti vrlo važna: u velikoj aplikaciji dodjeljivanje i uklanjanje mnogih objekata niti stvara značajne troškove upravljanja memorijom.

Pomoću radnih niti smanjujemo opće troškove nastale stvaranjem niti.

Da biste olakšali konfiguraciju bazena, ExecutorService dolazi s jednostavnim konstruktorom i nekim opcijama prilagodbe, kao što su vrsta reda, minimalni i maksimalni broj niti i njihova pravila imenovanja.

Za više detalja o ExecutorService, Molimo pročitajte naš Vodič za Java ExecutorService.

4. Pokretanje zadatka s izvršiteljima

Zahvaljujući ovom moćnom okviru možemo svoj način razmišljanja prebaciti s početnih niti na podnošenje zadataka.

Pogledajmo kako možemo izvršitelju predati asinkroni zadatak:

ExecutorService izvršitelj = Executors.newFixedThreadPool (10); ... izvršitelj.submit (() -> {novi zadatak ();});

Dvije su metode koje možemo koristiti: izvršiti, koji ne vraća ništa, i podnijeti, koji vraća a Budućnost inkapsulirajući rezultat izračuna.

Za više informacija o Budućnosti, molimo pročitajte naš Vodič za java.util.concurrent.Future.

5. Pokretanje zadatka sa CompletableFutures

Da biste dohvatili konačni rezultat iz a Budućnost objekt koji možemo koristiti dobiti metoda dostupna u objektu, ali to bi blokiralo nadređenu nit do kraja izračuna.

Alternativno, mogli bismo izbjeći blok dodavanjem više logike svom zadatku, ali moramo povećati složenost našeg koda.

Java 1.8 je uveo novi okvir povrh Budućnost konstrukcija za bolji rad s rezultatom izračuna: CompletableFuture.

CompletableFuture provodi CompletableStage, koji dodaje širok izbor metoda za pričvršćivanje povratnih poziva i izbjegavanje svih vodovoda potrebnih za pokretanje operacija na rezultatu nakon što je spreman.

Implementacija predavanja zadatka puno je jednostavnija:

CompletableFuture.supplyAsync (() -> "Zdravo");

supplyAsync traje a Dobavljač sadrži kôd koji želimo izvršiti asinkrono - u našem slučaju lambda parametar.

Zadatak je sada implicitno predan ForkJoinPool.commonPool (), ili možemo odrediti Izvršitelj preferiramo kao drugi parametar.

Da znam više o CompletableFuture, molimo pročitajte naš vodič za kompletibilnu budućnost.

6. Pokretanje odgođenih ili periodičnih zadataka

Kada radimo sa složenim web aplikacijama, možda ćemo trebati pokretati zadatke u određeno vrijeme, možda redovito.

Java ima nekoliko alata koji nam mogu pomoći u izvršavanju odgođenih ili ponavljajućih operacija:

  • java.util.Timer
  • java.util.concurrent.ScheduledThreadPoolExecutor

6.1. Tajmer

Tajmer je objekt za planiranje zadataka za buduće izvršavanje u pozadinskoj niti.

Zadaci se mogu zakazati za jednokratno izvršavanje ili za ponovljeno izvršavanje u redovitim intervalima.

Pogledajmo kako izgleda kod ako želimo pokrenuti zadatak nakon jedne sekunde kašnjenja:

Zadatak TimerTask = novi TimerTask () {public void run () {System.out.println ("Zadatak izveden:" + novi datum () + "n" + "Naziv teme:" + Thread.currentThread (). GetName ( )); }}; Timer timer = novi timer ("Timer"); dugo kašnjenje = 1000L; timer.schedule (zadatak, odgoda);

Dodajmo sada ponavljajući raspored:

timer.scheduleAtFixedRate (ponovljeni zadatak, kašnjenje, točka);

Ovaj put, zadatak će se pokrenuti nakon navedenog kašnjenja i ponavljat će se nakon proteklog vremenskog razdoblja.

Za više informacija pročitajte naš vodič za Java Timer.

6.2. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor ima metode slične Tajmer razred:

ScheduledExecutorService izvršiteljService = Izvršitelji.newScheduledThreadPool (2); ScheduledFuture resultFuture = executorService.schedule (callableTask, 1, TimeUnit.SECONDS);

Za kraj našeg primjera koristimo scheduleAtFixedRate () za ponavljajuće zadatke:

ScheduledFuture resultFuture = executorService.scheduleAtFixedRate (runnableTask, 100, 450, TimeUnit.MILLISECONDS);

Gornji kod izvršit će zadatak nakon početnog kašnjenja od 100 milisekundi, a nakon toga izvršit će isti zadatak svakih 450 milisekundi.

Ako procesor ne može završiti obradu zadatka na vrijeme prije sljedeće pojave, ScheduledExecutorService pričekat će dok se trenutni zadatak ne dovrši, prije početka sljedećeg.

Da bismo izbjegli ovo vrijeme čekanja, možemo koristiti scheduleWithFixedDelay (), koji, kako je opisano nazivom, jamči kašnjenje u fiksnoj duljini između iteracija zadatka.

Za više detalja o ScheduledExecutorService, molimo pročitajte naš Vodič za Java ExecutorService.

6.3. Koji je alat bolji?

Ako pokrenemo gornje primjere, rezultat izračuna izgleda isto.

Tako, kako odabrati pravi alat?

Kada okvir nudi više izbora, važno je razumjeti temeljnu tehnologiju da biste donijeli utemeljenu odluku.

Pokušajmo zaroniti malo dublje ispod haube.

Tajmer:

  • ne nudi jamstva u stvarnom vremenu: planira zadatke pomoću Object.wait (dugo) metoda
  • postoji jedna pozadinska nit, tako da se zadaci izvršavaju uzastopno, a dugotrajni zadatak može odgoditi druge
  • izuzeci tijekom izvođenja bačeni u a Zadatak odbrojavanja ubio bi jedinu dostupnu nit, pa bi ubio Tajmer

ScheduledThreadPoolExecutor:

  • može se konfigurirati s bilo kojim brojem niti
  • mogu iskoristiti sve dostupne CPU jezgre
  • hvata izuzete runtime i dopušta nam da ih rješavamo ako želimo (nadjačavanjem afterExecute metoda iz ThreadPoolExeecuter)
  • otkazuje zadatak koji je izbacio iznimku, dok drugima dopušta da se i dalje izvode
  • oslanja se na sustav zakazivanja OS-a kako bi pratio vremenske zone, kašnjenja, solarno vrijeme itd.
  • pruža suradnički API ako nam je potrebna koordinacija između više zadataka, poput čekanja na završetak svih poslanih zadataka
  • pruža bolji API za upravljanje životnim ciklusom niti

Izbor je sada očit, zar ne?

7. Razlika između Budućnost i ScheduledFuture

U našim primjerima koda to možemo primijetiti ScheduledThreadPoolExecutor vraća određenu vrstu Budućnost: ScheduledFuture.

ScheduledFuture proteže se oboje Budućnost i Odloženo sučelja, nasljeđujući tako dodatnu metodu getDelay koja vraća preostalo kašnjenje povezano s trenutnim zadatkom. Produžen je za RunnableScheduledFuture koja dodaje metodu za provjeru je li zadatak periodičan.

ScheduledThreadPoolExecutor provodi sve ove konstrukcije kroz unutarnju klasu ScheduledFutureTask i koristi ih za upravljanje životnim ciklusom zadatka.

8. Zaključci

U ovom uputstvu eksperimentirali smo s različitim dostupnim okvirima za paralelno pokretanje niti i izvršavanje zadataka.

Zatim smo dublje ušli u razlike između Tajmer i ScheduledThreadPoolExecutor.

Izvorni kôd članka dostupan je na GitHubu.