Cimer je odigrao partiju Honor of Kings i tako savladao Java višenitnost
Za Java početnike, mnogi koncepti višenitnosti zvuče teško za razumevanje. Na primer:
- Proces je enkapsulacija programa u toku izvršenja, osnovna jedinica za alokaciju i raspored resursa na sistemu, i omogućava konkurentnost operativnog sistema.
- Nit je podzadatak procesa, osnovna jedinica za CPU raspored i dodelu, i omogućava konkurentnost unutar procesa.
Prilično apstraktno, zar ne? Da uzmemo primer iz svakodnevnog života — zamislimo da igrate jednu partiju Honor of Kings-a (priznajem, ne umem da igram, haha):
- Proces možemo uporediti sa tom jednom partijom koju ste započeli.
- Nit možemo uporediti sa herojem kojeg ste izabrali, ili sa kristalima, čudovištima u šumi i sličnim elementima u igri.
Uz ovaj primer, pokušajmo da razumemo odnos između procesa i niti — jedan proces može imati više niti, i to se zove višenitnost. Zvuči mnogo jasnije, zar ne?
Proces i nit
1. Nit se izvršava u okviru procesa
(Pojedinačni heroj, šumsko čudovište ili vojnik ne mogu postojati samostalno)
2. Procesi ne utiču jedni na druge; završetak glavne niti dovodi do završetka celog procesa
(Dve partije igre nemaju međusobnu vezu ni uticaj. Kada vam sruše kristal, ta partija je gotova)
3. Podaci između različitih procesa se teško dele
(Dve partije igre teško imaju vezu — primer veze bi bio kada se protnik iz prošle partije ponovo pojavi u novoj)
4. Podaci između različitih niti unutar istog procesa se lako dele
(U partiji koju ste započeli možete videti status svakog igrača — živ ili mrtav — kao i njihove predmete i opremu)
5. Proces može ograničiti količinu iskorišćene memorije po adresama
(Moda sobe koju otvorite određuje koliko igrača može ući; kada se soba popuni, drugi ne mogu ući, osim ako neko ne izađe)
Tri načina za kreiranje niti
Pošto smo raščistili ove koncepte, pogledajmo tri načina za kreiranje više niti:
Nasleđivanje klase Thread
Korak 1: napravite klasu koja nasleđuje klasu Thread i pregazi metodu run.
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ": pobedio " + i + " vojnika");
}
}
}Napišimo test metodu da proverimo:
// kreiramo MyThread objekte
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
// postavljamo imena niti
t1.setName("Luban");
t2.setName("Liu Bei");
t3.setName("Arthur");
// pokrećemo niti
t1.start();
t2.start();
t3.start();Pogledajmo rezultat izvršenja:

Implementacija Runnable interfejsa
Korak 2: napravite klasu koja implementira Runnable interfejs i pregazi metodu run.
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {// sleep može baciti izuzetak, pa ga moramo eksplicitno obraditi
Thread.sleep(20);// pauza od 20 milisekundi
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " pobedio: " + i + " vojnika");
}
}
}Napišimo test metodu da proverimo:
// kreiramo MyRunnable klasu
MyRunnable mr = new MyRunnable();
// kreiramo Thread preko konstruktora sa argumentima i postavljamo ime niti
Thread t1 = new Thread(mr, "Zhang Fei");
Thread t2 = new Thread(mr, "Diao Chan");
Thread t3 = new Thread(mr, "Lu Bu");
// pokrećemo niti
t1.start();
t2.start();
t3.start();Pogledajmo rezultat izvršenja:

Implementacija Callable interfejsa
Korak 3: implementirajte Callable interfejs, pregazi metodu call — ovaj pristup omogućava dobijanje povratne vrednosti iz zadatka preko FutureTask.
public class CallerTask implements Callable<String> {
public String call() throws Exception {
return "Zdravo, pokrenut sam!";
}
public static void main(String[] args) {
// kreiramo asinhroni zadatak
FutureTask<String> task=new FutureTask<String>(new CallerTask());
// pokrećemo nit
new Thread(task).start();
try {
// čekamo završetak izvršenja i preuzimamo rezultat
String result=task.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}Neka pitanja o nitima
1. Zašto pregaziti metodu run?
Zato što podrazumevana metoda run() ne radi ništa.
Da bi nit izvršila neki stvarni zadatak, moramo pružiti sopstvenu implementaciju metode run(), odnosno moramo je pregaziti.
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread running");
}
}U ovom primeru pregazili smo metodu run() tako da ispiše jednu poruku. Kada kreiramo i pokrenemo instancu ove niti, isisaće tu poruku.
2. Koja je razlika između metode run i metode start?
run(): enkapsulira kod koji nit izvršava; direktno pozivanje je isto kao poziv obične metode.start(): pokreće nit, a zatim JVM poziva metodurun()te niti.
3. Šta je bolje — kreiranje više niti nasleđivanjem klase Thread ili implementacijom Runnable interfejsa?
Bolje je implementirati Runnable interfejs, iz dva razloga:
- Izbegava se ograničenje Java jednostavnog nasleđivanja — Java ne podržava višestruko nasleđivanje, tako da ako naša klasa već nasleđuje drugu klasu, ne može naslediti i klasu Thread.
- Pogodnije je kada više istih programskih kodova obrađuje isti resurs — nit, kod i podaci se efikasno razdvajaju, što više odgovara objektno-orijentisanoj paradigmi. Callable interfejs je vrlo sličan Runnable, ali može vratiti rezultat.
Druge metode za kontrolu niti
Za kontrolu niti često ćete sresti još 3 metode; hajde da ih redom predstavimo.
1) sleep()
Pauzira trenutno izvršavajuću nit na zadati broj milisekundi, odnosno prevodi je u stanje spavanja.
Treba obratiti pažnju na to da se pri pozivu sleep mora obraditi izuzetak.
try {// sleep može baciti izuzetak, pa ga moramo eksplicitno obraditi
Thread.sleep(20);// pauza od 20 milisekundi
} catch (InterruptedException e) {
e.printStackTrace();
}2) join()
Čeka da se ova nit završi pre nego što sledeća nit dobije pravo na CPU izvršenje; i ovde treba hvatati izuzetak.
// kreiramo MyRunnable klasu
MyRunnable mr = new MyRunnable();
// kreiramo Thread preko konstruktora sa argumentima i postavljamo ime niti
Thread t1 = new Thread(mr, "Zhang Fei");
Thread t2 = new Thread(mr, "Diao Chan");
Thread t3 = new Thread(mr, "Lu Bu");
// pokrećemo niti
t1.start();
try {
t1.join(); // čeka da se t1 završi pre nego što t2 i t3 krenu
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();Pogledajmo rezultat izvršenja:

3) setDaemon()
Označava ovu nit kao demon nit — tačnije, ona služi da opslužuje druge niti. Nit za sakupljanje smeća u Javi je tipičan primer demon niti.
// kreiramo MyRunnable klasu
MyRunnable mr = new MyRunnable();
// kreiramo Thread preko konstruktora sa argumentima i postavljamo ime niti
Thread t1 = new Thread(mr, "Zhang Fei");
Thread t2 = new Thread(mr, "Diao Chan");
Thread t3 = new Thread(mr, "Lu Bu");
t1.setDaemon(true);
t2.setDaemon(true);
// pokrećemo niti
t1.start();
t2.start();
t3.start();Kada se sve ostale niti završe i metoda main (glavna nit) završi, JVM će izaći, odnosno prestati sa radom. Ako je JVM zaustavljen, i demon niti se prirodno zaustavljaju.
4) yield()
Metoda yield() je statička metoda koja nagoveštava da je trenutna nit voljna da odustane od svog tekućeg vremenskog isečka i dozvoli drugim nitima da se izvrše. Međutim, to je samo predlog raspoređivaču niti, koji može taj predlog i da ignoriše. Konkretno ponašanje zavisi od operativnog sistema i strategije raspoređivanja niti u JVM-u.
class YieldExample {
public static void main(String[] args) {
Thread thread1 = new Thread(YieldExample::printNumbers, "Liu Bei");
Thread thread2 = new Thread(YieldExample::printNumbers, "Guan Yu");
thread1.start();
thread2.start();
}
private static void printNumbers() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
// kada je i parno, trenutna nit pauzira izvršenje
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + " ustupa kontrolu...");
Thread.yield();
}
}
}
}Rezultat izvršenja:

Iz ovog rezultata se vidi da, čak i kada nit ponekad ustupi kontrolu, druge niti ne moraju nužno da se izvrše.
Kratak pregled
Ovaj članak je uglavnom predstavio načine kreiranja Java niti i neke od najčešćih metoda niti. Na kraju, pogledajmo životni ciklus niti — jedna slika vredi više od hiljadu reči.

