Kako zaustaviti izvršenje nakon određenog vremena u Javi

1. Pregled

U ovom ćemo članku naučiti kako možemo završiti dugotrajnu egzekuciju nakon određenog vremena. Istražit ćemo različita rješenja ovog problema. Također ćemo pokriti neke od njihovih zamki.

2. Korištenje petlje

Zamislite da obrađujemo hrpu predmeta u petlji, poput nekih detalja o proizvodima u aplikaciji za e-trgovinu, ali da možda neće biti potrebno dovršiti sve stavke.

Zapravo bismo željeli obraditi samo do određenog vremena, a nakon toga želimo zaustaviti izvršenje i pokazati sve što je popis obrađivao do tog vremena.

Pogledajmo brzi primjer:

dugi početak = System.currentTimeMillis (); dugi kraj = početak + 30 * 1000; while (System.currentTimeMillis () <end) {// Neka skupa operacija na stavci. }

Ovdje će se petlja prekinuti ako je vrijeme premašilo ograničenje od 30 sekundi. U gore navedenom rješenju postoje neke značajne točke:

  • Niska točnost: Petlja može raditi dulje od nametnutog vremenskog ograničenja. To će ovisiti o vremenu koje svaka iteracija može potrajati. Na primjer, ako svaka iteracija može potrajati do 7 sekundi, tada ukupno vrijeme može potrajati i do 35 sekundi, što je oko 17% dulje od željenog vremenskog ograničenja od 30 sekundi
  • Blokiranje: Takva obrada u glavnoj niti možda nije dobra ideja jer će je dugo blokirati. Umjesto toga, ove bi operacije trebale biti odvojene od glavne niti

U sljedećem ćemo odjeljku razgovarati o tome kako pristup zasnovan na prekidima uklanja ova ograničenja.

3. Korištenje mehanizma prekida

Ovdje ćemo koristiti zasebnu nit za obavljanje dugotrajnih operacija. Glavna nit će poslati signal prekida radnoj niti prilikom isteka vremena.

Ako je radnička nit još uvijek živa, uhvatit će signal i zaustaviti njegovo izvršavanje. Ako radnik završi prije isteka vremena, to neće imati utjecaja na nit radnika.

Pogledajmo radničku nit:

klasa LongRunningTask implementira Runnable {@Override public void run () {try {while (! Thread.interrupted ()) {Thread.sleep (500); }} catch (InterruptedException e) {// pogreška dnevnika}}}

Ovdje, Konac.spavati simulira dugotrajnu operaciju. Umjesto ovoga, mogla bi postojati bilo koja druga operacija. Važno je da provjerite zastavicu prekida jer nisu sve operacije prekidajuće. Dakle, u tim bismo slučajevima trebali ručno provjeriti zastavicu.

Također, trebali bismo provjeriti ovu zastavicu u svakoj iteraciji kako bismo osigurali da se nit prestane izvršavati u roku od najviše jedne iteracije.

Dalje ćemo pokriti tri različita mehanizma slanja signala prekida.

3.1. Korištenje a Tajmer

Alternativno, možemo stvoriti Zadatak odbrojavanja za prekidanje radničke niti nakon isteka vremena:

klasa TimeOutTask proširuje TimerTask {private Thread t; privatni timer; TimeOutTask (nit t, odbrojavanje vremena) {this.t = t; this.timer = mjerač vremena; } public void run () {if (t! = null && t.isAlive ()) {t.interrupt (); timer.cancel (); }}}

Ovdje smo definirali a Zadatak odbrojavanja koja uzima radničku nit u trenutku nastanka. Hoće prekida radničku nit nakon poziva njezine trčanje metoda. The Tajmer će pokrenuti Zadatak odbrojavanja nakon navedenog kašnjenja:

Tema t = nova nit (nova LongRunningTask ()); Timer timer = novi Timer (); timer.schedule (novi TimeOutTask (t, timer), 30 * 1000); t.start ();

3.2. Korištenje metode Budućnost # get

Također možemo koristiti dobiti metoda a Budućnost umjesto da koristite a Tajmer:

ExecutorService izvršitelj = Executors.newSingleThreadExecutor (); Buduća budućnost = izvršitelj.submit (novi LongRunningTask ()); probajte {f.get (30, TimeUnit.SECONDS); } catch (TimeoutException e) {f.cancel (true); } napokon {service.shutdownNow (); }

Ovdje smo koristili ExecutorService poslati radničku nit koja vraća instancu Budućnost, čiji dobiti metoda će blokirati glavnu nit do određenog vremena. Podignut će a TimeoutException nakon navedenog vremenskog ograničenja. U ulov blok, prekidamo radničku nit pozivanjem datoteke otkazati metoda na Fizgovoriti objekt.

Glavna prednost ovog pristupa u odnosu na prethodni je taj što koristi spremište za upravljanje niti, dok Tajmer koristi samo jednu nit (bez spremišta).

3.3. Korištenje a ScheduledExcecutorSercvice

Možemo i koristiti ScheduledExecutorService da prekine zadatak. Ova klasa je produžetak ExecutorService i pruža istu funkcionalnost uz dodatak nekoliko metoda koje se bave raspoređivanjem izvršenja. To može izvršiti zadani zadatak nakon određenog kašnjenja postavljenih vremenskih jedinica:

ScheduledExecutorService izvršitelj = Izvršitelji.newScheduledThreadPool (2); Buduća budućnost = izvršitelj.submit (novi LongRunningTask ()); executor.schedule (new Runnable () {public void run () {future.cancel (true);}}, 1000, TimeUnit.MILLISECONDS); egzekutor.šutdown ();

Ovdje smo pomoću metode stvorili planirano spremište niti veličine dva newScheduledThreadPool. The ScheduledExecutorService #raspored metoda traje a Izvodljivo, vrijednost kašnjenja i jedinica kašnjenja.

Gornji program planira izvršavanje zadatka nakon jedne sekunde od trenutka predaje. Ovaj će zadatak otkazati izvorni dugotrajni zadatak.

Imajte na umu da za razliku od prethodnog pristupa, ne blokiramo glavnu nit pozivanjem datoteke Budućnost # get metoda. Stoga, to je najpoželjniji pristup među svim gore spomenutim pristupima.

4. Postoji li jamstvo?

Ne postoji jamstvo da će se izvršenje zaustaviti nakon određenog vremena. Glavni razlog je taj što nisu sve metode blokiranja prekidajuće. Zapravo postoji samo nekoliko dobro definiranih metoda koje se mogu prekinuti. Tako, ako se nit prekine i postavi zastava, ništa se drugo neće dogoditi dok ne dosegne jednu od ovih prekidajućih metoda.

Na primjer, čitati i pisati metode su prekidajuće samo ako se pozivaju na streamovima kreiranim s Prekidni kanal. BufferedReader nije an Prekidni kanal. Dakle, ako ga nit koristi za čitanje datoteke, pozivanje prekinuti() na ovoj niti blokiranoj u čitati metoda nema učinka.

Međutim, možemo eksplicitno provjeriti postoji li zastava prekida nakon svakog čitanja u petlji. To će dati razumnu sigurnost da zaustavite nit s određenim kašnjenjem. Ali, ovo ne garantira zaustavljanje niti nakon određenog vremena, jer ne znamo koliko vremena može potrajati operacija čitanja.

S druge strane, čekati metoda Objekt klasa je prekidna. Dakle, nit blokiran u čekati metoda će odmah baciti InterruptedException nakon postavljanja zastavice prekida.

Metode blokiranja možemo prepoznati tražeći bacaInterruptedException u njihovim potpisima metoda.

Jedan važan savjet je da izbjegavajte upotrebu zastarjelog Thread.stop () metoda. Zaustavljanjem niti otključava sve monitore koje je zaključao. To se događa zbog ThreadDeath iznimka koja se širi prema hrpi.

Ako je bilo koji od objekata koji su prethodno bili zaštićeni ovim monitorima bio u neskladnom stanju, nedosljedni objekti postaju vidljivi drugim nitima. To može dovesti do proizvoljnog ponašanja koje je vrlo teško otkriti i o kojem treba razmišljati.

5. Zaključak

U ovom uputstvu naučili smo razne tehnike zaustavljanja izvršenja nakon određenog vremena, zajedno sa prednostima i nedostacima svake od njih. Kompletni izvorni kod možete pronaći na GitHubu.