Duboko razumevanje Java klase za blokiranje i buđenje niti — LockSupport
LockSupport služi za blokiranje i buđenje niti; donja realizacija oslanja se na klasu Unsafe (detaljnije kasnije).
Ova klasa sadrži skup statičkih metoda za blokiranje i buđenje niti; one se uglavnom vrte oko park i unpark. Bez daljeg uvoda, pogledajmo jednostavan primer.
public class LockSupportDemo1 {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
// Kreira nit koja broji od 1 do 1000
Thread counterThread = new Thread(() -> {
for (int i = 1; i <= 1000; i++) {
System.out.println(i);
if (i == 500) {
// Kad broji do 500, budi glavnu nit
LockSupport.unpark(mainThread);
}
}
});
counterThread.start();
// Glavna nit poziva park
LockSupport.park();
System.out.println("Main thread was unparked.");
}
}U gornjem kodu, kad counterThread odbroji do 500, budi mainThread. mainThread se pri pozivu metode park blokira, dok ga neko ne unpark-uje.
LockSupport nema mnogo metoda; ovde ih sumiramo.
Blokiranje niti
void park(): blokira trenutnu nit; ako se pozove unpark ili se nit prekine, ta nit postaje izvršiva. Imajte na umu da park ne baca InterruptedException, pa nit mora posebno da proverava svoj status prekida.void park(Object blocker): funkcija ista kao kod metode 1, sa dodatnim argumentom Object koji beleži objekat zbog kojeg je nit blokirana, radi lakšeg otkrivanja problema.void parkNanos(long nanos): blokira trenutnu nit određeni broj nanosekundi, ili dok se ne pozove unpark, ili dok se nit ne prekine.void parkNanos(Object blocker, long nanos): funkcija ista kao kod metode 3, sa dodatnim argumentom Object koji beleži objekat zbog kojeg je nit blokirana, radi lakšeg otkrivanja problema.void parkUntil(long deadline): blokira trenutnu nit do određenog roka (u milisekundama), ili dok se ne pozove unpark, ili dok se nit ne prekine.void parkUntil(Object blocker, long deadline): funkcija ista kao kod metode 5, sa dodatnim argumentom Object koji beleži objekat zbog kojeg je nit blokirana, radi lakšeg otkrivanja problema.
Buđenje niti
void unpark(Thread thread): budi nit koju je blokirala metoda park. Ako nit nije blokirana, sledeći poziv park će odmah vratiti. To omogućava mehanizam buđenja „prečeći pred nos".
Zapravo, funkcija blokiranja i buđenja niti u LockSupport-u oslanja se na sun.misc.Unsafe, što je klasa veoma niskog nivoa; o njoj će kasnije biti detaljno reči. Na primer, metod park u LockSupport-u realizovan je preko metode unsafe.park().
Dump niti
„Dump niti" se obično odnosi na dobijanje detaljnog snimka trenutnog stanja niti i njenog steka poziva. To može pružiti važne podatke o tome šta nit radi i u kom delu koda se nalazi.
Sledi neki sadržaj koji se može naći u dump-u niti:
- ID i ime niti: jedinstveni identifikator i čitljivo ime niti.
- Stanje niti: trenutno stanje niti, npr. running (RUNNABLE), waiting (WAITING), sleeping (TIMED_WAITING) ili blocked (BLOCKED).
- Stek poziva: praćenje steka poziva niti, prikazuje kompletan niz poziva metoda od trenutne tačke izvršavanja do inicijalnog poziva.
- Podaci o bravi: ako nit čeka na bravu ili je drži, dump obično sadrži podatke o tim bravama.
Dump niti može se dobiti na različite načine, npr. pomoću Java alata jstack ili iz alata poput Java VisualVM, Java Mission Control.
Sledi jednostavan primer: blokiramo nit preko LockSupport-a, a zatim preko Intellij IDEA pregledamo dump niti.
public class LockSupportDemo {
public static void main(String[] args) {
LockSupport.park();
}
}Pokrenite, a zatim u Run panelu kliknite na „attach debugger".

Zatim u debugger panelu desnim klikom odaberite „export thread".

Tako možete videti dump niti.

Dump niti pozivom metode park():
"main" #1 prio=5 os_prio=0 tid=0x02cdcc00 nid=0x2b48 waiting on condition [0x00d6f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at learn.LockSupportDemo.main(LockSupportDemo.java:7)Dump niti pozivom metode park(Object blocker)
"main" #1 prio=5 os_prio=0 tid=0x0069cc00 nid=0x6c0 waiting on condition [0x00dcf000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x048c2d18> (a java.lang.String)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at learn.LockSupportDemo.main(LockSupportDemo.java:7)Uporedbom dump-a niti nakon poziva park bez argumenta i sa argumentom vidi se da park sa Object-om, u poređenju sa park bez argumenta, dodaje informaciju parking to wait for <0x048c2d18> (a java.lang.String). Ta vrsta informacije je poput „beleženja mesta zločina" i pomaže nam programerima da brzo otkrijemo i rešimo problem.
Zanimljivo je da je Java 1.5, prilikom uvođenja LockSupport-a, izostavila opis informacije o blokiranju, pa je to naknadno dopunjeno u Javi 1.6.
Razlika u odnosu na synchronized
Još nešto na šta treba paziti: synchronized blokira nit, koja prelazi u stanje BLOCKED, dok poziv metoda LockSupport-a radi blokiranje niti prelazi u stanje WAITING.
Pogledajmo jednostavan primer kako se koristi.
public class LockSupportExamples {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread is parked now");
LockSupport.park();
System.out.println("Thread is unparked now");
});
thread.start();
try {
Thread.sleep(3000); // glavna nit čeka 3 sekunde
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(thread); // glavna nit budi blokiranu nit
}
}Nit thread poziva LockSupport.park() čime se blokira; kad glavna nit nakon 3 sekunde spavanja završi, preko metode LockSupport.unpark(thread) budi nit thread, koja po buđenju nastavlja s narednim operacijama. Uz to, LockSupport.unpark(thread) može specificirati objekat niti koja se budi.
Rezultat rada:
Thread is parked now
Thread is unparked nowPristup dizajna
LockSupport je dizajniran preko „dozvole", poput ulaska automobila na autoput: na ulazu se preuzima kartica, na izlazu se predaje; bez kartice ne možete izaći, mada možete nadoknaditi jednu karticu.
LockSupport za nit koja ga koristi vezuje stanje dozvole (permit); semantika permit-a je „da li se ima dozvola" — 0 znači ne, 1 znači da; podrazumevano je 0.
LockSupport.unpark: dozvola vezana za zadatu nit direktno se postavlja na 1; ako je pre ažuriranjapermit<1, budi se zadužena nit.LockSupport.park: ako je dozvola vezana za trenutnu nit >0, odmah je postavlja na 0; inače blokira trenutnu nit.
Pogledajmo vremensku liniju:

- Nit A izvrši
LockSupport.park, vidi da je permit 0, nema dozvolu — blokira nit A. - Nit B izvrši
LockSupport.unpark(argument nit A), postavlja dozvolu za nit A, permit postaje 1, budi nit A. - Nit B završava tok.
- Nit A se budi, vidi da je permit 1, potroši dozvolu, permit se postavlja na 0.
- Nit A izvršava kritični deo.
- Nit A završava tok.
Iz gore navedene analize sledi zaključak: semantika unpark-a jasno je „dati niti dozvolu", a semantika park-a jasno je „potrošiti dozvolu koju nit drži". Zato redosled unpark i park nije strogo propisan; dovoljno je dobro kontrolisati niti koje se koriste. Tok unpark=>park izgleda ovako:

- permit je podrazumevano 0; nit A izvrši LockSupport.unpark, permit postaje 1, nit A ima dozvolu.
- Nit A izvrši LockSupport.park; pošto je permit 1, potroši dozvolu, permit se postavlja na 0.
- Izvršava kritični deo.
- Završava tok.
Na kraju, još jedna napomena o park: nit blokirana zbog park ne budi samo unpark; može je probuditi i prekid niti (Thread.interrupt), a pri tome se ne baca InterruptedException. Zato se preporučuje da nakon park-a sami proverite status prekida niti i izvršite odgovarajuću obradu.
Zašto se preporučuje LockSupport za blokiranje i buđenje niti (saradnju među nitima)? Zato što ima sledeće prednosti:
- Operacija nad nitima bolje odgovara intuitivnoj semantici blokiranja niti.
- Operacija je preciznija — tačno se može probuditi određena nit (notify budi nasumično jednu nit, notifyAll budi sve niti na čekanju).
- Nije potrebno takmičiti se za objekat brave (operator je nit), pa ne nastaju mrtve blokade zbog takmičenja za objekat brave.
- unpark i park nemaju strog redosled izvršavanja, pa ne izazivaju mrtve blokade zbog redosleda; na primer, „Thread.suspend i Thread.resume", ako se ne izvrše u strogom redosledu, mogu izazvati mrtvu blokadu.
Pitanje sa intervjua
Intervjuer iz Alibaba-e: postoje 3 nezavisne niti; jedna ispisuje samo A, druga samo B, treća samo C; uz tri pokrenute niti, na razuman način ih naterajte da redom ispisuju ABCABC.
public class ABCPrinter {
private static Thread t1, t2, t3;
public static void main(String[] args) {
t1 = new Thread(() -> {
for (int i = 0; i < 2; i++) {
LockSupport.park();
System.out.print("A");
LockSupport.unpark(t2);
}
});
t2 = new Thread(() -> {
for (int i = 0; i < 2; i++) {
LockSupport.park();
System.out.print("B");
LockSupport.unpark(t3);
}
});
t3 = new Thread(() -> {
for (int i = 0; i < 2; i++) {
LockSupport.park();
System.out.print("C");
LockSupport.unpark(t1);
}
});
t1.start();
t2.start();
t3.start();
// Glavna nit malo sačeka, da osigura da su ostale niti startovane i ušle u park stanje.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Pokreće čitav tok
LockSupport.unpark(t1);
}
}Način realizacije ovde je:
- Prvo za svaku nit kreiramo Runnable koji u petlji park-uje samu sebe, zatim ispisuje svoj znak i unpark-uje sledeću nit.
- Sve niti nakon startovanja prvo pozivaju park i blokiraju sebe.
- Glavna nit, nakon malog kašnjenja, poziva unpark nad t1 i pokreće čitav tok ispisa. Tako se osigurava da svaka nit radi po očekivanom redosledu.
Zaključak
LockSupport pruža niži i fleksibilniji način raspoređivanja niti. Ne zavisi od sinhronizovanog bloka niti od specifičnog objekta brave. Može se koristiti za izgradnju složenijih sinhronizacionih struktura, poput prilagođenih brava ili istovremenih kontejnera. Kombinacija LockSupport.park i LockSupport.unpark olakšava preciznu kontrolu između niti, bez složene logike sinhronizacije i nadgledanja objekata.
Urednik: Chenmo Wang Er; sadržaj pre uređivanja uglavnom potiče iz GitHub repozitorijuma CL0610 https://github.com/CL0610/Java-concurrency, a deo sadržaja i slika potiče od čitaoca Astronomera LockSupport za početnike, toplo preporučujemo.
