Invoering
We weten dus dat Java threads heeft. Dat lees je in de recensie Better together: Java and the Thread class. Deel I — Draden van executie . Threads zijn nodig om parallel werk uit te voeren. Dit maakt het zeer waarschijnlijk dat de threads op de een of andere manier met elkaar zullen interageren. Laten we eens kijken hoe dit gebeurt en welke basishulpmiddelen we hebben.![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 1](https://cdn.codegym.cc/images/article/8ff41897-b282-41cc-8431-850cef1dcb82/800.jpeg)
Opbrengst
Thread.yield() is verbijsterend en wordt zelden gebruikt. Op internet wordt het op veel verschillende manieren beschreven. Waaronder enkele mensen die schrijven dat er een rij threads is, waarin een thread zal afdalen op basis van threadprioriteiten. Andere mensen schrijven dat een thread zijn status zal veranderen van "Running" naar "Runnable" (ook al is er geen onderscheid tussen deze statussen, dwz dat Java er geen onderscheid tussen maakt). De realiteit is dat het allemaal veel minder bekend is en in zekere zin toch eenvoudiger.![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 2](https://cdn.codegym.cc/images/article/3959a540-1de0-4152-8c65-56e54084864e/800.jpeg)
yield()
documentatie van de methode. Als je het leest, is het duidelijk dat deyield()
methode geeft eigenlijk alleen maar een aanbeveling aan de Java-threadplanner dat deze thread minder uitvoeringstijd kan krijgen. Maar wat er werkelijk gebeurt, dwz of de planner handelt naar de aanbeveling en wat hij in het algemeen doet, hangt af van de implementatie van de JVM en het besturingssysteem. En het kan ook van een aantal andere factoren afhangen. Alle verwarring is hoogstwaarschijnlijk te wijten aan het feit dat multithreading opnieuw is bedacht naarmate de Java-taal zich heeft ontwikkeld. Lees hier meer in het overzicht: Korte introductie tot Java Thread.yield() .
Slaap
Een thread kan tijdens de uitvoering in de slaapstand gaan. Dit is de gemakkelijkste vorm van interactie met andere threads. Het besturingssysteem dat de virtuele Java-machine uitvoert waarop onze Java-code draait, heeft zijn eigen threadplanner . Het beslist welke thread moet worden gestart en wanneer. Een programmeur kan niet rechtstreeks vanuit Java-code communiceren met deze planner, alleen via de JVM. Hij of zij kan de planner vragen om de thread even te pauzeren, dwz in slaapstand te zetten. U kunt meer lezen in deze artikelen: Thread.sleep() en Hoe multithreading werkt . U kunt ook bekijken hoe threads werken in Windows-besturingssystemen: Internals of Windows Thread . En laten we het nu met onze eigen ogen zien. Sla de volgende code op in een bestand met de naamHelloWorldApp.java
:
class HelloWorldApp {
public static void main(String []args) {
Runnable task = () -> {
try {
int secToWait = 1000 * 60;
Thread.currentThread().sleep(secToWait);
System.out.println("Woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(task);
thread.start();
}
}
Zoals je kunt zien, hebben we een taak die 60 seconden wacht, waarna het programma eindigt. We compileren met het commando " javac HelloWorldApp.java
" en voeren het programma vervolgens uit met " java HelloWorldApp
". Het programma start je best in een apart venster. Op Windows is het bijvoorbeeld zo: start java HelloWorldApp
. We gebruiken de opdracht jps om de PID (proces-ID) op te halen en we openen de lijst met threads met " jvisualvm --openpid pid
: ![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 3](https://cdn.codegym.cc/images/article/4b17ebd6-9a1e-480f-89fc-d452b946ddf5/800.jpeg)
try {
TimeUnit.SECONDS.sleep(60);
System.out.println("Woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
Is het je opgevallen dat we InterruptedException
overal aan het afhandelen zijn? Laten we begrijpen waarom.
Draad.interrupt()
Het punt is dat terwijl een thread wacht / slaapt, iemand misschien wil onderbreken. In dit geval behandelen we eenInterruptedException
. Dit mechanisme is in het leven geroepen nadat de Thread.stop()
methode Deprecated was verklaard, dwz verouderd en ongewenst. De reden was dat toen de stop()
methode werd aangeroepen, de thread simpelweg werd "gedood", wat erg onvoorspelbaar was. We konden niet weten wanneer de thread zou worden gestopt en we konden de gegevensconsistentie niet garanderen. Stel je voor dat je gegevens naar een bestand schrijft terwijl de thread wordt gedood. In plaats van de draad te doden, besloten de makers van Java dat het logischer zou zijn om hem te vertellen dat hij onderbroken moest worden. Hoe te reageren op deze informatie is een zaak voor de thread zelf om te beslissen. Lees voor meer informatie Waarom is Thread.stop verouderd?op de website van Oracle. Laten we naar een voorbeeld kijken:
public static void main(String []args) {
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
};
Thread thread = new Thread(task);
thread.start();
thread.interrupt();
}
In dit voorbeeld wachten we geen 60 seconden. In plaats daarvan geven we onmiddellijk "Onderbroken" weer. Dit komt omdat we de interrupt()
methode op de thread hebben genoemd. Deze methode stelt een interne vlag in met de naam "onderbrekingsstatus". Dat wil zeggen, elke thread heeft een interne vlag die niet direct toegankelijk is. Maar we hebben native methoden voor interactie met deze vlag. Maar dat is niet de enige manier. Een thread kan actief zijn, niet ergens op wachten, maar gewoon acties uitvoeren. Maar het kan anticiperen dat anderen zijn werk op een bepaald moment willen beëindigen. Bijvoorbeeld:
public static void main(String []args) {
Runnable task = () -> {
while(!Thread.currentThread().isInterrupted()) {
// Do some work
}
System.out.println("Finished");
};
Thread thread = new Thread(task);
thread.start();
thread.interrupt();
}
In het bovenstaande voorbeeld while
wordt de lus uitgevoerd totdat de thread extern wordt onderbroken. Wat betreft de isInterrupted
vlag, het is belangrijk om te weten dat als we een vangen InterruptedException
, de isInterrupted-vlag wordt gereset en vervolgens isInterrupted()
false retourneert. De klasse Thread heeft ook een statische methode Thread.interrupted() die alleen van toepassing is op de huidige thread, maar deze methode zet de vlag terug op false! Lees meer in dit hoofdstuk getiteld Thread Interruption .
Join (Wacht tot een andere thread klaar is)
De eenvoudigste vorm van wachten is wachten tot een andere thread klaar is.public static void main(String []args) throws InterruptedException {
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
};
Thread thread = new Thread(task);
thread.start();
thread.join();
System.out.println("Finished");
}
In dit voorbeeld slaapt de nieuwe thread 5 seconden. Tegelijkertijd wacht de hoofddraad tot de slapende draad wakker wordt en klaar is met zijn werk. Als je de status van de thread in JVisualVM bekijkt, ziet het er zo uit: ![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 4](https://cdn.codegym.cc/images/article/3d7d682c-18f0-481e-a205-3205776fabb3/800.jpeg)
join
methode is vrij eenvoudig, omdat het slechts een methode met Java-code is die wordt uitgevoerd wait()
zolang de thread waarop deze wordt aangeroepen actief is. Zodra de draad sterft (wanneer hij klaar is met zijn werk), wordt het wachten onderbroken. En dat is alle magie van de join()
methode. Dus laten we verder gaan met het meest interessante.
Monitor
Multithreading omvat het concept van een monitor. Het woord monitor komt in het Engels via het 16e-eeuwse Latijn en betekent "een instrument of apparaat dat wordt gebruikt voor het observeren, controleren of continu bijhouden van een proces". In de context van dit artikel zullen we proberen de basis te behandelen. Voor iedereen die de details wil, duik in de gelinkte materialen. We beginnen onze reis met de Java Language Specification (JLS): 17.1. synchronisatie . Het zegt het volgende:![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 5](https://cdn.codegym.cc/images/article/601d44a1-4581-43b8-89b7-50c818ddd8ac/1024.jpeg)
lock()
of vrijgeven met unlock()
. Vervolgens vinden we de tutorial op de Oracle-website: Intrinsieke sloten en synchronisatie. Deze tutorial zegt dat de synchronisatie van Java is opgebouwd rond een interne entiteit die een intrinsieke vergrendeling of monitorvergrendeling wordt genoemd . Dit slot wordt vaak simpelweg een " monitor " genoemd. We zien ook weer dat elk object in Java een intrinsiek slot heeft. U kunt Java-intrinsieke vergrendelingen en synchronisatie lezen . Vervolgens is het belangrijk om te begrijpen hoe een object in Java aan een monitor kan worden gekoppeld. In Java heeft elk object een header die interne metadata opslaat die niet beschikbaar zijn voor de programmeur vanuit de code, maar die de virtuele machine nodig heeft om correct met objecten te werken. De objectkop bevat een "markeerwoord", dat er als volgt uitziet: ![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 6](https://cdn.codegym.cc/images/article/b710063c-14d9-4589-8ec0-db29cbe7ed99/512.jpeg)
https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf
public class HelloWorld{
public static void main(String []args){
Object object = new Object();
synchronized(object) {
System.out.println("Hello World");
}
}
}
Hier gebruikt de huidige thread (degene waarop deze regels code worden uitgevoerd) het synchronized
trefwoord om te proberen de monitor te gebruiken die is gekoppeld aan deobject"\
variabele om het slot te krijgen/verkrijgen. Als niemand anders strijdt om de monitor (dwz niemand anders draait gesynchroniseerde code met hetzelfde object), dan kan Java proberen een optimalisatie uit te voeren die "biased locking" wordt genoemd. Een relevante tag en een record over welke thread eigenaar is van het slot van de monitor worden toegevoegd aan het markeringswoord in de objectkop. Dit vermindert de overhead die nodig is om een monitor te vergrendelen. Als de monitor voorheen eigendom was van een andere thread, is een dergelijke vergrendeling niet voldoende. De JVM schakelt over naar het volgende type vergrendeling: "basisvergrendeling". Het maakt gebruik van Compare-and-Swap (CAS) operaties. Bovendien slaat het markeringswoord van de objectkop zelf niet langer het markeringswoord op, maar eerder een verwijzing naar waar het is opgeslagen, en de tag verandert zodat de JVM begrijpt dat we basisvergrendeling gebruiken. Als meerdere threads strijden (strijden) om een monitor (de ene heeft het slot verkregen en een tweede wacht tot het slot wordt vrijgegeven), dan verandert de tag in het merkwoord en slaat het merkwoord nu een verwijzing naar de monitor op als een object — een interne entiteit van de JVM. Zoals vermeld in het JDK Enchancement Proposal (JEP), vereist deze situatie ruimte in het Native Heap-geheugengebied om deze entiteit op te slaan. De verwijzing naar de geheugenlocatie van deze interne entiteit wordt opgeslagen in het markeringswoord van de objectkop. Een monitor is dus eigenlijk een mechanisme voor het synchroniseren van toegang tot gedeelde bronnen tussen meerdere threads. De JVM schakelt tussen verschillende implementaties van dit mechanisme. Dus, voor de eenvoud, als we het over de monitor hebben, hebben we het eigenlijk over sloten. en een seconde wacht tot het slot wordt vrijgegeven), dan verandert de tag in het merkwoord, en het merkwoord slaat nu een verwijzing naar de monitor op als een object - een interne entiteit van de JVM. Zoals vermeld in het JDK Enchancement Proposal (JEP), vereist deze situatie ruimte in het Native Heap-geheugengebied om deze entiteit op te slaan. De verwijzing naar de geheugenlocatie van deze interne entiteit wordt opgeslagen in het markeringswoord van de objectkop. Een monitor is dus eigenlijk een mechanisme voor het synchroniseren van toegang tot gedeelde bronnen tussen meerdere threads. De JVM schakelt tussen verschillende implementaties van dit mechanisme. Dus, voor de eenvoud, als we het over de monitor hebben, hebben we het eigenlijk over sloten. en een seconde wacht tot het slot wordt vrijgegeven), dan verandert de tag in het merkwoord, en het merkwoord slaat nu een verwijzing naar de monitor op als een object - een interne entiteit van de JVM. Zoals vermeld in het JDK Enchancement Proposal (JEP), vereist deze situatie ruimte in het Native Heap-geheugengebied om deze entiteit op te slaan. De verwijzing naar de geheugenlocatie van deze interne entiteit wordt opgeslagen in het markeringswoord van de objectkop. Een monitor is dus eigenlijk een mechanisme voor het synchroniseren van toegang tot gedeelde bronnen tussen meerdere threads. De JVM schakelt tussen verschillende implementaties van dit mechanisme. Dus, voor de eenvoud, als we het over de monitor hebben, hebben we het eigenlijk over sloten. en het merkwoord slaat nu een verwijzing naar de monitor op als een object - een interne entiteit van de JVM. Zoals vermeld in het JDK Enchancement Proposal (JEP), vereist deze situatie ruimte in het Native Heap-geheugengebied om deze entiteit op te slaan. De verwijzing naar de geheugenlocatie van deze interne entiteit wordt opgeslagen in het markeringswoord van de objectkop. Een monitor is dus eigenlijk een mechanisme voor het synchroniseren van toegang tot gedeelde bronnen tussen meerdere threads. De JVM schakelt tussen verschillende implementaties van dit mechanisme. Dus, voor de eenvoud, als we het over de monitor hebben, hebben we het eigenlijk over sloten. en het merkwoord slaat nu een verwijzing naar de monitor op als een object - een interne entiteit van de JVM. Zoals vermeld in het JDK Enchancement Proposal (JEP), vereist deze situatie ruimte in het Native Heap-geheugengebied om deze entiteit op te slaan. De verwijzing naar de geheugenlocatie van deze interne entiteit wordt opgeslagen in het markeringswoord van de objectkop. Een monitor is dus eigenlijk een mechanisme voor het synchroniseren van toegang tot gedeelde bronnen tussen meerdere threads. De JVM schakelt tussen verschillende implementaties van dit mechanisme. Dus, voor de eenvoud, als we het over de monitor hebben, hebben we het eigenlijk over sloten. De verwijzing naar de geheugenlocatie van deze interne entiteit wordt opgeslagen in het markeringswoord van de objectkop. Een monitor is dus eigenlijk een mechanisme voor het synchroniseren van toegang tot gedeelde bronnen tussen meerdere threads. De JVM schakelt tussen verschillende implementaties van dit mechanisme. Dus, voor de eenvoud, als we het over de monitor hebben, hebben we het eigenlijk over sloten. De verwijzing naar de geheugenlocatie van deze interne entiteit wordt opgeslagen in het markeringswoord van de objectkop. Een monitor is dus eigenlijk een mechanisme voor het synchroniseren van toegang tot gedeelde bronnen tussen meerdere threads. De JVM schakelt tussen verschillende implementaties van dit mechanisme. Dus, voor de eenvoud, als we het over de monitor hebben, hebben we het eigenlijk over sloten. ![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 7](https://cdn.codegym.cc/images/article/16d7cedb-ef29-4828-9a44-78ef3433e559/800.jpeg)
Gesynchroniseerd (wachten op een slot)
Zoals we eerder zagen, hangt het concept van een "gesynchroniseerd blok" (of "kritieke sectie") nauw samen met het concept van een monitor. Bekijk een voorbeeld:public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Runnable task = () -> {
synchronized(lock) {
System.out.println("thread");
}
};
Thread th1 = new Thread(task);
th1.start();
synchronized(lock) {
for (int i = 0; i < 8; i++) {
Thread.currentThread().sleep(1000);
System.out.print(" " + i);
}
System.out.println(" ...");
}
}
Hier geeft de hoofdthread eerst het taakobject door aan de nieuwe thread en verkrijgt dan onmiddellijk de vergrendeling en voert er een lange operatie mee uit (8 seconden). Al die tijd kan de taak niet doorgaan, omdat hij het synchronized
blok niet kan betreden, omdat de vergrendeling al is verworven. Als de thread de vergrendeling niet kan krijgen, wacht deze op de monitor. Zodra het de vergrendeling krijgt, gaat het door met de uitvoering. Wanneer een thread een monitor verlaat, wordt de vergrendeling opgeheven. In JVisualVM ziet het er zo uit: ![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 8](https://cdn.codegym.cc/images/article/93ab7c13-e090-46d8-b343-16054436f6e2/800.jpeg)
th1.getState()
instructie in de for-lus retourneert BLOCKED , omdat zolang de lus loopt, de lock
monitor van het object wordt bezet door de main
thread en de th1
thread is geblokkeerd en kan niet doorgaan totdat de vergrendeling is opgeheven. Naast gesynchroniseerde blokken kan een hele methode worden gesynchroniseerd. Hier is bijvoorbeeld een methode uit de HashTable
klas:
public synchronized int size() {
return count;
}
Deze methode wordt op elk moment door slechts één thread uitgevoerd. Hebben we het slot echt nodig? Ja, we hebben het nodig. In het geval van instantiemethoden fungeert het "dit"-object (huidige object) als een slot. Er is hier een interessante discussie over dit onderwerp: Is er een voordeel aan het gebruik van een gesynchroniseerde methode in plaats van een gesynchroniseerd blok? . Als de methode statisch is, is het slot niet het "dit"-object (omdat er geen "dit"-object is voor een statische methode), maar eerder een Class-object (bijvoorbeeld ) Integer.class
.
Wacht (wacht op een monitor). melding() en meldingAll() methoden
De klasse Thread heeft een andere wachtmethode die is gekoppeld aan een monitor. In tegenstelling totsleep()
en join()
kan deze methode niet zomaar worden aangeroepen. De naam is wait()
. De wait
methode wordt aangeroepen op het object dat is gekoppeld aan de monitor waarop we willen wachten. Laten we een voorbeeld bekijken:
public static void main(String []args) throws InterruptedException {
Object lock = new Object();
// The task object will wait until it is notified via lock
Runnable task = () -> {
synchronized(lock) {
try {
lock.wait();
} catch(InterruptedException e) {
System.out.println("interrupted");
}
}
// After we are notified, we will wait until we can acquire the lock
System.out.println("thread");
};
Thread taskThread = new Thread(task);
taskThread.start();
// We sleep. Then we acquire the lock, notify, and release the lock
Thread.currentThread().sleep(3000);
System.out.println("main");
synchronized(lock) {
lock.notify();
}
}
In JVisualVM ziet het er zo uit: ![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 10](https://cdn.codegym.cc/images/article/2245dec1-e120-4a9a-a274-a760fb7f7bf3/800.jpeg)
wait()
en notify()
zijn gekoppeld aan java.lang.Object
. Het lijkt misschien vreemd dat threadgerelateerde methoden in de Object
klas zitten. Maar de reden daarvoor ontvouwt zich nu. U zult zich herinneren dat elk object in Java een header heeft. De header bevat diverse huishoudelijke informatie, waaronder informatie over de monitor, dwz de status van het slot. Onthoud dat elk object of instantie van een klasse is gekoppeld aan een interne entiteit in de JVM, een intrinsieke vergrendeling of monitor genoemd. In het bovenstaande voorbeeld geeft de code voor het taakobject aan dat we het gesynchroniseerde blok invoeren voor de monitor die aan het lock
object is gekoppeld. Als we erin slagen het slot voor deze monitor te bemachtigen, danwait()
wordt genoemd. De thread die de taak uitvoert, geeft de monitor van het object vrij lock
, maar komt in de wachtrij van threads die wachten op een melding van de lock
monitor van het object. Deze rij threads wordt een WAIT SET genoemd, wat het doel beter weergeeft. Dat wil zeggen, het is meer een set dan een wachtrij. De main
thread maakt een nieuwe thread aan met het taakobject, start deze en wacht 3 seconden. Dit maakt het zeer waarschijnlijk dat de nieuwe thread de vergrendeling vóór de thread kan verkrijgen main
en in de wachtrij van de monitor kan komen. Daarna gaat de main
thread zelf het lock
gesynchroniseerde blok van het object binnen en voert threadmelding uit met behulp van de monitor. Nadat de melding is verzonden, main
geeft de thread hetlock
monitor van het object en de nieuwe thread, die eerder wachtte tot de lock
monitor van het object werd vrijgegeven, gaat verder met de uitvoering. Het is mogelijk om een melding te sturen naar slechts één thread ( notify()
) of gelijktijdig naar alle threads in de wachtrij ( notifyAll()
). Lees hier meer: Verschil tussen notificatie() en notificatieAll() in Java . Het is belangrijk op te merken dat de meldingsvolgorde afhangt van hoe de JVM is geïmplementeerd. Lees hier meer: Hoe hongersnood op te lossen met attenderen en all notificeren? . Synchronisatie kan worden uitgevoerd zonder een object op te geven. U kunt dit doen wanneer een hele methode wordt gesynchroniseerd in plaats van een enkel codeblok. Voor statische methoden is het slot bijvoorbeeld een Class-object (verkregen via .class
):
public static synchronized void printA() {
System.out.println("A");
}
public static void printB() {
synchronized(HelloWorld.class) {
System.out.println("B");
}
}
Wat betreft het gebruik van sloten, zijn beide methoden hetzelfde. Als een methode niet statisch is, wordt de synchronisatie uitgevoerd met behulp van de huidige instance
, dat wil zeggen met this
. Trouwens, we zeiden eerder dat je de getState()
methode kunt gebruiken om de status van een thread te krijgen. Voor een thread in de wachtrij die wacht op een monitor, is de status bijvoorbeeld WAITING of TIMED_WAITING, als de wait()
methode een time-out specificeert. ![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 11](https://cdn.codegym.cc/images/article/6dc08067-eb01-42a8-a091-5290b2582a21/512.jpeg)
https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java
Draad levenscyclus
In de loop van zijn leven verandert de status van een thread. In feite omvatten deze veranderingen de levenscyclus van de draad. Zodra een thread is aangemaakt, is de status NIEUW. In deze toestand is de nieuwe thread nog niet actief en weet de Java-threadplanner er nog niets van. Om ervoor te zorgen dat de threadplanner meer te weten komt over de thread, moet u dethread.start()
methode aanroepen. Vervolgens gaat de thread over naar de status RUNNABLE. Het internet heeft veel onjuiste diagrammen die onderscheid maken tussen de status "Runnable" en "Running". Maar dit is een vergissing, omdat Java geen onderscheid maakt tussen "klaar om te werken" (uitvoerbaar) en "werkend" (actief). Wanneer een thread leeft maar niet actief is (niet uitvoerbaar), bevindt deze zich in een van de volgende twee toestanden:
- GEBLOKKEERD — wachtend om een kritieke sectie binnen te gaan, dwz een
synchronized
blok. - WACHTEN - wachten op een andere thread om aan een bepaalde voorwaarde te voldoen.
getState()
. Threads hebben ook een isAlive()
methode die true retourneert als de thread niet BEËINDIGD is.
LockSupport en draad parkeren
Vanaf Java 1.6 verscheen een interessant mechanisme genaamd LockSupport .![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 12](https://cdn.codegym.cc/images/article/345e2fbe-17f2-470d-89a2-a82a711136b8/800.jpeg)
park()
methode keert onmiddellijk terug als de vergunning beschikbaar is, waarbij de vergunning in het proces wordt verbruikt. Anders blokkeert het. Het aanroepen van de unpark
methode maakt de vergunning beschikbaar als deze nog niet beschikbaar is. Er is maar 1 vergunning. De Java-documentatie voor LockSupport
verwijst naar de Semaphore
klasse. Laten we een eenvoudig voorbeeld bekijken:
import java.util.concurrent.Semaphore;
public class HelloWorldApp{
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(0);
try {
semaphore.acquire();
} catch (InterruptedException e) {
// Request the permit and wait until we get it
e.printStackTrace();
}
System.out.println("Hello, World!");
}
}
Deze code zal altijd wachten, omdat de semafoor nu 0 permits heeft. En wanneer acquire()
de code wordt ingeroepen (dwz de vergunning aanvragen), wacht de thread totdat deze de vergunning ontvangt. Omdat we wachten, moeten we omgaan met InterruptedException
. Interessant is dat de semafoor een aparte threadstatus krijgt. Als we in JVisualVM kijken, zien we dat de status niet "Wait" is, maar "Parkeren". ![Samen beter: Java en de klasse Thread. Deel II — Synchronisatie - 13](https://cdn.codegym.cc/images/article/937f0e6b-f11a-4bb8-a965-734d52d198e4/800.jpeg)
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
// Park the current thread
System.err.println("Will be Parked");
LockSupport.park();
// As soon as we are unparked, we will start to act
System.err.println("Unparked");
};
Thread th = new Thread(task);
th.start();
Thread.currentThread().sleep(2000);
System.err.println("Thread state: " + th.getState());
LockSupport.unpark(th);
Thread.currentThread().sleep(2000);
}
De status van de thread is WACHT, maar JVisualVM maakt onderscheid tussen wait
het synchronized
trefwoord en park
de LockSupport
klasse. Waarom is dit LockSupport
zo belangrijk? We keren terug naar de Java-documentatie en kijken naar de status van de WACHTENDE thread. Zoals je kunt zien, zijn er maar drie manieren om erin te komen. Twee van die manieren zijn wait()
en join()
. En de derde is LockSupport
. In Java kunnen ook sloten worden gebouwd LockSuppor
en tools van een hoger niveau bieden. Laten we proberen er een te gebruiken. Kijk bijvoorbeeld eens op ReentrantLock
:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{
public static void main(String []args) throws InterruptedException {
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
System.out.println("Thread");
lock.unlock();
};
lock.lock();
Thread th = new Thread(task);
th.start();
System.out.println("main");
Thread.currentThread().sleep(2000);
lock.unlock();
}
}
Net als in de vorige voorbeelden is alles hier eenvoudig. Het lock
object wacht tot iemand de gedeelde bron vrijgeeft. Als we in JVisualVM kijken, zien we dat de nieuwe thread wordt geparkeerd totdat de main
thread de vergrendeling ervan opheft. U kunt hier meer lezen over sloten: Java 8 StampedLocks vs. ReadWriteLocks en Synchronized and Lock API in Java. Om beter te begrijpen hoe vergrendelingen worden geïmplementeerd, is het handig om over Phaser te lezen in dit artikel: Guide to the Java Phaser . En over verschillende synchronizers gesproken, u moet het DZone- artikel over The Java Synchronizers lezen.
Conclusie
In deze review hebben we de belangrijkste manieren onderzocht waarop threads in Java interageren. Aanvullend materiaal:- Samen beter: Java en de klasse Thread. Deel I — Draden van executie
- https://dzone.com/articles/the-java-synchronizers
- https://www.javatpoint.com/java-multithreading-interview-questions