6 stanja Java niti i prelaze između njih (detaljno objašnjeno)
Hajde da prvo pogledamo prelaz stanja niti u operativnom sistemu. U operativnom sistemu se nit posmatra kao laki proces, pa su stanja niti zapravo ista kao i stanja procesa.

Niti operativnog sistema uglavnom imaju sledeća tri stanja:
- Spremno (ready): nit čeka na korišćenje CPU-a; nakon poziva od strane raspoređivača ulazi u stanje running.
- Izvršavanje (running): nit koristi CPU.
- Čekanje (waiting): nit čeka na poziv događaja za čekanje ili čeka druge resurse (npr. U/I).
Zatim da pogledamo 6 stanja Java niti:
// Izvorni kod Thread.State
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}NEW
Nit u stanju NEW još uvek nije pokrenuta. Pod „još uvek nije pokrenuta" podrazumeva se da metoda start() instance Thread još nije pozvana.
private void testStateNew() {
Thread thread = new Thread(() -> {});
System.out.println(thread.getState()); // ispisuje NEW
}Iz gornjeg primera se vidi da je samo kreirana nit, bez poziva metode start — tada je nit u stanju NEW.
Dva povezana pitanja o start
- Da li je moguće višestruko pozivati metodu start iste niti?
- Ako se nit završi (tada je u stanju TERMINATED), da li je moguće ponovo pozvati metodu start te niti?
Da bismo analizirali ova pitanja, hajde da pogledamo izvorni kod metode start():
// synchronized ključna reč osigurava da je ova metod bezbedan za niti
public synchronized void start() {
// threadStatus != 0 označava da je nit već pokrenuta ili je već završila
// ako pokušamo ponovo da pokrenemo tu nit, biće bačen IllegalThreadStateException
if (threadStatus != 0)
throw new IllegalThreadStateException();
// dodaje ovu nit u grupu niti tekuće niti
group.add(this);
// proglašava promenljivu kojom se beleži da li je nit uspešno pokrenuta
boolean started = false;
try {
// pokreće nit pomoću native metoda
start0();
// ako nije bačen izuzetak, started se postavlja na true, što znači da je start uspeo
started = true;
} finally {
// u finally bloku se, bez obzira na to da li je try blok bacio izuzetak, izvršava sledeće
try {
// ako nit nije uspešno pokrenuta, uklanja se iz grupe niti
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// ako pri uklanjanju niti nastane izuzetak, ignorišemo ga
}
}
}Vidimo da unutar start() postoji promenljiva threadStatus. Ako nije jednaka 0, poziv start() direktno baca izuzetak.
Dalje, postoji native metod start0(). Ovaj metod ne vrši nikakvu obradu nad threadStatus. Ovde deluje kao da smo nemoćni pred threadStatus, ali hajde da pogledamo kroz debug:
@Test
public void testStartMethod() {
Thread thread = new Thread(() -> {});
thread.start(); // prvi poziv
thread.start(); // drugi poziv
}Postavimo breakpoint na samom početku metode start:
- Pri prvom pozivu vrednost threadStatus je 0.
- Pri drugom pozivu vrednost threadStatus nije 0.
Pogledajmo izvorni kod koji proverava trenutno stanje niti:
// Izvorni kod Thread.getState:
public State getState() {
// get current thread state
return sun.misc.VM.toThreadState(threadStatus);
}
// Izvorni kod sun.misc.VM:
// Ako se stanje niti AND-uje sa 4 i rezultat nije 0, nit je u RUNNABLE stanju.
// Ako se stanje niti AND-uje sa 1024 i rezultat nije 0, nit je u BLOCKED stanju.
// Ako se stanje niti AND-uje sa 16 i rezultat nije 0, nit je u WAITING stanju.
// Ako se stanje niti AND-uje sa 32 i rezultat nije 0, nit je u TIMED_WAITING stanju.
// Ako se stanje niti AND-uje sa 2 i rezultat nije 0, nit je u TERMINATED stanju.
// Konačno, ako se stanje niti AND-uje sa 1 i rezultat je 0, nit je u NEW stanju, inače je u RUNNABLE stanju.
public static State toThreadState(int var0) {
if ((var0 & 4) != 0) {
return State.RUNNABLE;
} else if ((var0 & 1024) != 0) {
return State.BLOCKED;
} else if ((var0 & 16) != 0) {
return State.WAITING;
} else if ((var0 & 32) != 0) {
return State.TIMED_WAITING;
} else if ((var0 & 2) != 0) {
return State.TERMINATED;
} else {
return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
}
}Sećate li se naša dva pitanja?
- Da li je moguće višestruko pozivati metodu start iste niti?
- Ako se nit završi (tada je u stanju TERMINATED), da li je moguće ponovo pozvati metodu start te niti?
Na osnovu gornjeg izvornog koda, odgovori su:
- Oba su nemoguća — nakon poziva start vrednost threadStatus se menja (
threadStatus != 0), pa će sledeći poziv start baciti izuzetak IllegalThreadStateException. - Vrednost threadStatus jednaka 2 označava da je trenutno stanje niti TERMINATED (o čemu će biti reči ispod).
RUNNABLE
Označava da je trenutna nit u toku izvršenja. Nit u stanju RUNNABLE se izvršava u Java virtuelnoj mašini, a može i da čeka na alokaciju resursa od strane CPU-a.
Pogledajmo definiciju stanja RUNNABLE u izvornom kodu klase Thread:
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/Značenje bi trebalo da bude jasno; ako nije, prevedite (a zapravo smo već preveli).
Dakle, RUNNABLE stanje Java niti zapravo obuhvata i ready i running stanja niti operativnog sistema.
BLOCKED
Stanje blokade. Nit u stanju BLOCKED čeka na oslobađanje brave (o bravama će detaljno biti reči kasnije) kako bi ušla u sinhronizovani region.
Ilustrujmo stanje BLOCKED svakodnevnim primerom:
Zamislimo da ste posle posla otišli u menzu na ručak. Dođete do jedinog šanka u menzi i vidite da je ispred vas već neko. Morate sačekati da on ode.
Pretpostavimo da ste vi nit t2, a osoba ispred vas nit t1. Tada t1 drži bravu (jedini šank u menzi), t2 čeka na oslobađanje brave, pa je t2 u stanju BLOCKED.
WAITING
Stanje čekanja. Da bi nit u stanju čekanja prešla u RUNNABLE, potrebno je da je druga nit probudi.
Poziv sledeće tri metode dovodi nit u stanje čekanja:
Object.wait(): prevodi trenutnu nit u stanje čekanja dok je druga nit ne probudi;Thread.join(): čeka da se nit završi; interno poziva metod wait iz klase Object;LockSupport.park(): osim ako ne dobije dozvolu za poziv, onemogućava raspoređivanje trenutne niti. O LockSupport-u će detaljno biti reči kasnije.
Nastavimo primer odozgo da objasnimo stanje WAITING:
Čekali ste nekoliko minuta i konačno ste na redu, a onda se iznenada pojavi jedan „bezobzirni" menadžer. Čim ga vidite, osetite zlu kob, i zaista — on traži vas.
Odvede vas u stranu i kaže vam da ručate kasnije, jer on popodne ima prezentaciju i mora hitno sa vama da prođe stanje projekta. Iako vam se to nimalo ne sviđa, ipak odlazite od šanka.
Pretpostavimo da ste i dalje nit t2, a vaš menadžer nit t1. Iako ste držali bravu (šank), pojavio se „nepozvani gost" i morate da oslobodite bravu. Tada je vaše t2 stanje WAITING. Zatim menadžer t1 acquire-uje bravu i prelazi u stanje RUNNABLE.
Ako menadžer t1 ne probudi vas t2 (notify, notifyAll...), ostaje vam samo da čekate u nedogled.
TIMED_WAITING
Stanje vremenski ograničenog čekanja. Nit čeka zadati vremenski period; po isteku vremena se automatski budi.
Poziv sledećih metoda dovodi nit u stanje vremenski ograničenog čekanja:
Thread.sleep(long millis): uspava trenutnu nit na zadato vreme;Object.wait(long timeout): nit spava zadato vreme, a tokom čekanja se može probuditi putemnotify()/notifyAll();Thread.join(long millis): čeka da se trenutna nit izvrši najviše millis milisekundi; ako je millis 0, čeka dok se ne završi;LockSupport.parkNanos(long nanos): osim ako ne dobije dozvolu za poziv, onemogućava raspoređivanje trenutne niti na zadato vreme; o LockSupport-u će detaljno biti reči kasnije;LockSupport.parkUntil(long deadline): isto kao prethodno — zabranjuje raspoređivanje niti na zadato vreme;
Nastavimo primer odozgo da objasnimo stanje TIMED_WAITING:
Sledećeg dana u podne, opet je vreme za ručak i opet ste kod šanka.
Odjednom se setite da vas je kolega zamolio da ga sačekate — rekao je da ga sačekate deset minuta dok ispravi jedan bag.
Pa, dobro, sačekaćemo — pa odstupite od šanka. Brzo prođe deset minuta, a njega nema; pomislite da ste već toliko čekali i da ipak idete da ručate.
Tada ste i dalje nit t1, a kolega koji ispravlja bag je nit t2. t2 je naterao t1 da čeka zadato vreme, pa je t1 tokom tog čekanja u stanju TIMED_WAITING.
Nakon što t1 sačeka 10 minuta, automatski se budi i stiče pravo da se bori za bravu.
TERMINATED
Završno stanje. Tada je nit već završila izvršenje.
Prelaz stanja niti
Na osnovu gornjeg opisa stanja niti, možemo izvesti sledeći dijagram prelaza stanja niti:

Prelaz između BLOCKED i RUNNABLE stanja
Rekli smo: nit u stanju BLOCKED čeka na oslobađanje brave. Pretpostavimo da imamo dve niti a i b; nit a je prva acquire-ovala bravu i još je nije oslobodila, pa je tada b u stanju BLOCKED. Pogledajmo primer:
@Test
public void blockedTest() {
Thread a = new Thread(new Runnable() {
@Override
public void run() {
testMethod();
}
}, "a");
Thread b = new Thread(new Runnable() {
@Override
public void run() {
testMethod();
}
}, "b");
a.start();
b.start();
System.out.println(a.getName() + ":" + a.getState()); // ispis?
System.out.println(b.getName() + ":" + b.getState()); // ispis?
}
// sinhronizovani metod se bori za bravu
private synchronized void testMethod() {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}Na prvi pogled bi se moglo učiniti da će nit a prva pozvati sinhronizovani metod, a sinhronizovani metod zatim poziva Thread.sleep(), pa će nužno ispisati TIMED_WAITING, dok će nit b, pošto čeka da nit a oslobodi bravu, nužno ispisati BLOCKED.
Međutim, nije tako; treba obratiti pažnju na dve stvari:
- Prvo, u test metodi
blockedTest()postoji i glavna (main) nit - Drugo, nakon pokretanja niti je potrebno izvesno vreme da bi se počelo sa izvršenjem metode run.
Glavna nit test metode samo garantuje da su niti a i b pozvale metod start (prešle u stanje RUNNABLE); ako CPU bude dovoljno brz, pre nego što dve niti zaista počnu da se bore za bravu, već će se ispisati njihova trenutna stanja (RUNNABLE).
Naravno, ako CPU bude sporiji, neka od niti može ispisati i stanje BLOCKED (tada su dve niti već počele borbu za bravu).
Evo poređenja rezultata iz nekoliko mojih izvršavanja:

Sada možda pitate: kako da postignem da se ispiše stanje BLOCKED?
Stanje BLOCKED nastaje tek kada dve niti stupe u borbu za bravu. Stoga je dovoljno da malo „odmorimo" glavnu nit u test metodi — pozovemo Thread.sleep().
Treba obratiti pažnju na to koliko dugo glavna nit spava — vreme mora biti unutar intervala borbe za bravu; ne smete čekati da prethodna nit oslobodi bravu pa tek onda da se borite za nju, jer u tom slučaju nećete dobiti stanje BLOCKED.
Izmenimo gornji test metod blockedTest:
public void blockedTest() throws InterruptedException {
······
a.start();
Thread.sleep(1000L); // obratite pažnju: ovde glavna nit spava 1000 milisekundi, a testMethod spava 2000 milisekundi
b.start();
System.out.println(a.getName() + ":" + a.getState()); // ispis?
System.out.println(b.getName() + ":" + b.getState()); // ispis?
}Rezultat izvršenja je prikazan ispod:

U ovom primeru, prelazi stanja dveju niti su sledeći:
- Prelaz stanja niti a: RUNNABLE (
a.start()) -> TIMED_WAITING (Thread.sleep()) -> RUNNABLE (isticanjesleep()) -> BLOCKED (nije dohvatio bravu) -> TERMINATED - Prelaz stanja niti b: RUNNABLE (
b.start()) -> BLOCKED (nije dohvatio bravu) -> TERMINATED
Kurziv označava stanja koja se mogu pojaviti; isprobajte nekoliko puta na svom računaru. Isto tako, i ovde rezultat može imati više varijanti.
Prelaz između stanja WAITING i RUNNABLE
Iz dijagrama prelaza znamo da 3 metode mogu prevesti nit iz RUNNABLE u WAITING. Ovde ćemo uglavnom predstaviti Object.wait() i Thread.join().
Object.wait()
Pre poziva metoda wait() nit mora držati bravu nad objektom.
Kada nit pozove wait(), oslobodiće tekuću bravu, sve dok druga nit ne pozove notify()/notifyAll() kako bi probudila niti koje čekaju na bravu.
Treba napomenuti da poziv notify() budi samo jednu nit koja čeka na bravu; ako više niti čeka tu bravu, nije sigurno da će biti probuđena upravo ona nit koja je ranije pozvala wait().
Isto tako, nakon što notifyAll() probudi sve niti koje čekaju na bravu, ne mora odmah prepustiti vremenski isečak onoj niti koja je malopre oslobodila bravu — to zavisi od rasporeda sistema.
Thread.join()
Poziv metoda join() znači da će se čekati da se ova nit završi (prelazak u stanje TERMINATED).
Izmenimo deo gornjeg primera gde se nit pokreće:
public void blockedTest() {
······
a.start();
a.join();
b.start();
System.out.println(a.getName() + ":" + a.getState()); // ispis TERMINATED
System.out.println(b.getName() + ":" + b.getState());
}Da nismo pozvali metod join, glavna nit bi nastavila dalje bez obzira na to da li se nit a završila.
Nakon što se nit a pokrene, odmah se poziva metod join, pa će ovde glavna nit sačekati da se nit a završi, pa će ispisano stanje niti a nužno biti TERMINATED.
Što se tiče stanja niti b, može ispisati RUNNABLE (još nije ušla u sinhronizovani metod), a može i TIMED_WAITING (ušla je u sinhronizovani metod).
Prelaz između TIMED_WAITING i RUNNABLE stanja
TIMED_WAITING je slično WAITING stanju, s tom razlikom što vreme čekanja za TIMED_WAITING jeste zadato.
Thread.sleep(long)
Uspava trenutnu nit na zadato vreme. Treba napomenuti da ovde „spavanje" samo privremeno zaustavlja izvršenje niti, a ne oslobađa bravu. Po isteku vremena, nit ponovo ulazi u stanje RUNNABLE.
Object.wait(long)
Metod wait(long) prevodi nit u stanje TIMED_WAITING. Ono što wait(long) deli sa bezparametarskim metodom wait() jeste da oba mogu biti probuđena pozivom notify() ili notifyAll() druge niti.
Razlika je u tome što parametrizovani metod wait(long), čak i ako ga druga nit ne probudi, nakon isteka zadatog vremena long sam sebe budi i stiče pravo na borbu za bravu.
Thread.join(long)
join(long) čini da se trenutna nit izvršava zadato vreme i prevodi nit u stanje TIMED_WAITING.
Izmenimo ponovo prethodni primer:
public void blockedTest() {
······
a.start();
a.join(1000L);
b.start();
System.out.println(a.getName() + ":" + a.getState()); // ispis TIMED_WAITING
System.out.println(b.getName() + ":" + b.getState());
}Ovde se poziva a.join(1000L), pošto je zadato konkretno vreme izvršenja niti a, a to vreme je kraće od vremena koje nit a provodi u sleep, pa će stanje niti a ispisati TIMED_WAITING.
Stanje niti b i dalje nije fiksno (RUNNABLE ili BLOCKED).
Prekid niti
U nekim situacijama, nakon što pokrenemo nit, shvatimo da ne želimo da ona nastavi sa izvršenjem, pa je potrebno prekinuti. Trenutno u Javi ne postoji bezbedan metod za direktno zaustavljanje niti, ali Java pruža mehanizam prekida niti za obradu situacija koje zahtevaju prekid.
Mehanizam prekida niti je kooperativan. Treba napomenuti da prekid ne zaustavlja direktno nit, već obaveštava nit koja treba biti prekinuta da to sama obradi.
Kratko predstavimo nekoliko metoda klase Thread vezanih za prekid niti:
Thread.interrupt(): prekid niti. Ovaj prekid ne zaustavlja nit odmah, već postavlja status prekida niti na true (podrazumevano je false);Thread.isInterrupted(): proverava da li je trenutna nit prekinuta.Thread.interrupted(): proverava da li je trenutna nit prekinuta; za razliku odisInterrupted(), ako otkrije da je trenutna nit prekinuta, očistiće status prekida niti.
U mehanizmu prekida niti, kada druga nit obavesti nit koja treba biti prekinuta, status prekida te niti se postavlja na true, ali kako će ta nit tačno da odreaguje, u potpunosti odlučuje sama — može u pogodnom trenutku prihvatiti zahtev za prekid, ali i potpuno ignorisati zahtev i nastaviti izvršenje.
Kratak pregled
Ovaj članak je detaljno objasnio 6 stanja Java niti — novo, izvršavajuće, blokirano, čekanje, vremenski ograničeno čekanje i završeno — kao i proces prelaska između tih stanja.
Autor: Chenmo Wang Er. Originalni sadržaj potiče iz otvorenog repozitorijuma prijatelja Xiao Qi Ying Huo Chong: Duboko i pristupačno o Java višenitnosti, toplo preporučujemo.
