CodeGym /Java Blog /Willekeurig /Samen beter: Java en de klasse Thread. Deel II — Synchron...
John Squirrels
Niveau 41
San Francisco

Samen beter: Java en de klasse Thread. Deel II — Synchronisatie

Gepubliceerd in de groep Willekeurig

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

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 - 2Er is een bug ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) vastgelegd voor de 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 naam HelloWorldApp.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 - 3Zoals je kunt zien heeft onze thread nu de status "Slapen". Er is zelfs een elegantere manier om te helpen onze thread hebben zoete dromen:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Is het je opgevallen dat we InterruptedExceptionoveral 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 een InterruptedException. 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 whilewordt de lus uitgevoerd totdat de thread extern wordt onderbroken. Wat betreft de isInterruptedvlag, 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 - 4Dankzij monitoringtools kun je zien wat er met de thread aan de hand is. De joinmethode 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 - 5Het blijkt dat Java een "monitor"-mechanisme gebruikt voor synchronisatie tussen threads. Aan elk object is een monitor gekoppeld en threads kunnen deze verkrijgen met 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://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Hier is een JavaWorld-artikel dat erg handig is: Hoe de virtuele Java-machine threadsynchronisatie uitvoert . Dit artikel moet worden gecombineerd met de beschrijving uit de sectie "Samenvatting" van de volgende uitgave van het JDK-bugvolgsysteem: JDK-8183909 . U kunt hetzelfde hier lezen: JEP-8183909 . Dus in Java is een monitor gekoppeld aan een object en wordt deze gebruikt om een ​​thread te blokkeren wanneer de thread de vergrendeling probeert te verkrijgen (of te krijgen). Hier is het eenvoudigste voorbeeld:

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 synchronizedtrefwoord 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

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 synchronizedblok 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 - 8Zoals u kunt zien in JVisualVM, is de status "Monitor", wat betekent dat de thread is geblokkeerd en de monitor niet kan gebruiken. U kunt ook code gebruiken om de status van een thread te bepalen, maar de statusnamen die op deze manier worden bepaald, komen niet overeen met de namen die in JVisualVM worden gebruikt, hoewel ze vergelijkbaar zijn. In dit geval deth1.getState()instructie in de for-lus retourneert BLOCKED , omdat zolang de lus loopt, de lockmonitor van het object wordt bezet door de mainthread en de th1thread 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 HashTableklas:

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 tot sleep()en join()kan deze methode niet zomaar worden aangeroepen. De naam is wait(). De waitmethode 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 - 10Om te begrijpen hoe dit werkt, moet u onthouden dat de methoden wait()en notify()zijn gekoppeld aan java.lang.Object. Het lijkt misschien vreemd dat threadgerelateerde methoden in de Objectklas 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 lockobject 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 lockmonitor 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 mainthread 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 mainen in de wachtrij van de monitor kan komen. Daarna gaat de mainthread zelf het lockgesynchroniseerde blok van het object binnen en voert threadmelding uit met behulp van de monitor. Nadat de melding is verzonden, maingeeft de thread hetlockmonitor van het object en de nieuwe thread, die eerder wachtte tot de lockmonitor 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://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 de thread.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 synchronizedblok.
  • WACHTEN - wachten op een andere thread om aan een bepaalde voorwaarde te voldoen.
Als aan de voorwaarde is voldaan, start de threadplanner de thread. Als de thread tot een bepaalde tijd wacht, is de status TIMED_WAITING. Als de thread niet meer actief is (deze is voltooid of er is een uitzondering gegenereerd), krijgt deze de status BEËINDIGD. Gebruik de methode om de status van een thread te achterhalen 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 - 12Deze klasse koppelt een "permit" aan elke thread die er gebruik van maakt. Een aanroep naar de 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 unparkmethode maakt de vergunning beschikbaar als deze nog niet beschikbaar is. Er is maar 1 vergunning. De Java-documentatie voor LockSupportverwijst naar de Semaphoreklasse. 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 - 13Laten we nog een voorbeeld bekijken:

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 waithet synchronizedtrefwoord en parkde LockSupportklasse. Waarom is dit LockSupportzo 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 LockSupporen 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 lockobject wacht tot iemand de gedeelde bron vrijgeeft. Als we in JVisualVM kijken, zien we dat de nieuwe thread wordt geparkeerd totdat de mainthread 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 — Uitvoeringsthreads Beter samen: Java en de klasse Thread. Deel III — Interactie Samen beter: Java en de klasse Thread. Deel IV — Callable, Future en vrienden Samen beter: Java en de Thread-klasse. Deel V — Uitvoerder, ThreadPool, Fork/Join Beter samen: Java en de Thread-klasse. Deel VI — Vuur weg!
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION