CodeGym /Java-blogg /Tilfeldig /Bedre sammen: Java og Thread-klassen. Del II – Synkronise...
John Squirrels
Nivå
San Francisco

Bedre sammen: Java og Thread-klassen. Del II – Synkronisering

Publisert i gruppen

Introduksjon

Så vi vet at Java har tråder. Det kan du lese om i anmeldelsen Bedre sammen: Java og trådklassen. Del I – Tråder om henrettelse . Tråder er nødvendig for å utføre arbeid parallelt. Dette gjør det høyst sannsynlig at trådene på en eller annen måte vil samhandle med hverandre. La oss se på hvordan dette skjer og hvilke grunnleggende verktøy vi har. Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 1

Utbytte

Thread.yield() er forvirrende og brukes sjelden. Det er beskrevet på mange forskjellige måter på Internett. Inkludert noen som skriver at det er en eller annen kø med tråder, der en tråd vil gå ned basert på trådprioriteringer. Andre skriver at en tråd vil endre status fra «Kjøres» til «Kjørbar» (selv om det ikke er noe skille mellom disse statusene, dvs. Java skiller ikke mellom dem). Realiteten er at det hele er mye mindre kjent og likevel enklere på en måte. Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 2Det er en feil ( JDK-6416721: (spesifikasjonstråd) Fix Thread.yield() javadoc ) logget for yield()metodens dokumentasjon. Hvis du leser den, er det tydelig atyield()metoden gir faktisk bare en anbefaling til Java-trådplanleggeren om at denne tråden kan gis mindre utførelsestid. Men hva som faktisk skjer, altså om planleggeren handler etter anbefalingen og hva den gjør generelt, avhenger av JVMs implementering og operativsystemet. Og det kan avhenge av noen andre faktorer også. All forvirringen skyldes mest sannsynlig at multithreading har blitt tenkt nytt etter hvert som Java-språket har utviklet seg. Les mer i oversikten her: Kort introduksjon til Java Thread.yield() .

Sove

En tråd kan gå i dvale under utførelsen. Dette er den enkleste typen interaksjon med andre tråder. Operativsystemet som kjører den virtuelle Java-maskinen som kjører Java-koden vår, har sin egen trådplanlegger . Den bestemmer hvilken tråd som skal startes og når. En programmerer kan ikke samhandle med denne planleggeren direkte fra Java-kode, bare gjennom JVM. Han eller hun kan be planleggeren om å sette tråden på pause en stund, dvs. legge den i dvale. Du kan lese mer i disse artiklene: Thread.sleep() og Hvordan multithreading fungerer . Du kan også sjekke ut hvordan tråder fungerer i Windows-operativsystemer: Internaler i Windows Thread . Og la oss nå se det med egne øyne. Lagre følgende kode i en fil som heter 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();
    }
}
Som du kan se har vi en oppgave som venter i 60 sekunder, hvoretter programmet avsluttes. Vi kompilerer ved å bruke kommandoen " javac HelloWorldApp.java" og kjører deretter programmet med " java HelloWorldApp". Det er best å starte programmet i et eget vindu. For eksempel, på Windows, er det slik: start java HelloWorldApp. Vi bruker jps-kommandoen for å få PID (prosess-ID), og vi åpner listen over tråder med " jvisualvm --openpid pid: Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 3Som du kan se har tråden vår nå statusen "Sovende". Faktisk er det en mer elegant måte å hjelpe tråden vår har søte drømmer:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
La du merke til at vi håndterer InterruptedExceptionoveralt? La oss forstå hvorfor.

Thread.interrupt()

Saken er at mens en tråd venter/sover kan det være noen som vil avbryte. I dette tilfellet håndterer vi en InterruptedException. Denne mekanismen ble opprettet etter at Thread.stop()metoden ble erklært utdatert, dvs. utdatert og uønsket. Årsaken var at når stop()metoden ble kalt, ble tråden rett og slett «drept», noe som var svært uforutsigbart. Vi kunne ikke vite når tråden ville bli stoppet, og vi kunne ikke garantere datakonsistens. Tenk deg at du skriver data til en fil mens tråden er drept. I stedet for å drepe tråden, bestemte Javas skapere at det ville være mer logisk å fortelle den at den skulle avbrytes. Hvordan man skal svare på denne informasjonen er en sak opp til tråden selv å avgjøre. For mer informasjon, les Hvorfor er Thread.stop avviklet?på Oracles nettside. La oss se på et eksempel:

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();
}
I dette eksemplet venter vi ikke 60 sekunder. I stedet vil vi umiddelbart vise "Avbrutt". Dette er fordi vi kalte interrupt()metoden på tråden. Denne metoden setter et internt flagg kalt "avbruddsstatus". Det vil si at hver tråd har et internt flagg som ikke er direkte tilgjengelig. Men vi har innfødte metoder for å samhandle med dette flagget. Men det er ikke den eneste måten. En tråd kan kjøre, ikke venter på noe, bare utføre handlinger. Men den kan forutse at andre vil ønske å avslutte arbeidet på et bestemt tidspunkt. For eksempel:

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();
}
I eksemplet ovenfor whilevil løkken bli utført til tråden avbrytes eksternt. Når det gjelder isInterruptedflagget, er det viktig å vite at hvis vi fanger en InterruptedException, blir isInterrupted-flagget tilbakestilt, og isInterrupted()vil deretter returnere falskt. Thread-klassen har også en statisk Thread.interrupted()- metode som bare gjelder den gjeldende tråden, men denne metoden tilbakestiller flagget til false! Les mer i dette kapittelet med tittelen Trådavbrudd .

Bli med (vent til en annen tråd er ferdig)

Den enkleste typen venting er å vente på at en annen tråd skal fullføres.

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");
}
I dette eksemplet vil den nye tråden sove i 5 sekunder. Samtidig vil hovedtråden vente til den sovende tråden våkner og fullfører arbeidet. Hvis du ser på trådens tilstand i JVisualVM, så vil den se slik ut: Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 4Takket være overvåkingsverktøy kan du se hva som skjer med tråden. Metoden joiner ganske enkel, fordi det bare er en metode med Java-kode som kjøres wait()så lenge tråden den kalles på er i live. Så snart tråden dør (når den er ferdig med arbeidet), avbrytes ventetiden. Og det er hele magien med metoden join(). Så la oss gå videre til det mest interessante.

Observere

Multithreading inkluderer konseptet med en skjerm. Ordet monitor kommer til engelsk ved hjelp av latin fra 1500-tallet og betyr "et instrument eller en enhet som brukes til å observere, sjekke eller holde en kontinuerlig oversikt over en prosess". I sammenheng med denne artikkelen vil vi prøve å dekke det grunnleggende. For alle som vil ha detaljene, vennligst dykk ned i det tilknyttede materialet. Vi begynner vår reise med Java Language Specification (JLS): 17.1. Synkronisering . Det står følgende: Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 5Det viser seg at Java bruker en "monitor"-mekanisme for synkronisering mellom tråder. En monitor er knyttet til hvert objekt, og tråder kan hente det med lock()eller frigjøre det med unlock(). Deretter finner vi opplæringen på Oracle-nettstedet: Intrinsic Locks and Synchronization. Denne opplæringen sier at Javas synkronisering er bygget rundt en intern enhet kalt en indre lås eller skjermlås . Denne låsen kalles ofte ganske enkelt en " monitor ". Vi ser også igjen at hvert objekt i Java har en egen lås knyttet til seg. Du kan lese Java - Intrinsic Locks and Synchronization . Deretter vil det være viktig å forstå hvordan et objekt i Java kan assosieres med en skjerm. I Java har hvert objekt en header som lagrer interne metadata som ikke er tilgjengelige for programmereren fra koden, men som den virtuelle maskinen trenger for å fungere korrekt med objekter. Objektoverskriften inneholder et "merkeord", som ser slik ut: Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Her er en JavaWorld-artikkel som er veldig nyttig: Hvordan den virtuelle Java-maskinen utfører trådsynkronisering . Denne artikkelen bør kombineres med beskrivelsen fra "Sammendrag"-delen av følgende utgave i JDK-feilsporingssystemet: JDK-8183909 . Du kan lese det samme her: JEP-8183909 . Så i Java er en skjerm assosiert med et objekt og brukes til å blokkere en tråd når tråden prøver å skaffe (eller få) låsen. Her er det enkleste eksemplet:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Her bruker den nåværende tråden (den som disse kodelinjene kjøres på) nøkkelordet synchronizedfor å forsøke å bruke monitoren som er knyttet tilobject"\variabel for å få/erverve låsen. Hvis ingen andre kjemper for skjermen (dvs. ingen andre kjører synkronisert kode ved å bruke samme objekt), kan Java prøve å utføre en optimalisering som kalles "biased locking". En relevant tag og en post om hvilken tråd som eier monitorens lås legges til merkeordet i objektoverskriften. Dette reduserer kostnadene som kreves for å låse en skjerm. Hvis skjermen tidligere var eid av en annen tråd, er slik låsing ikke nok. JVM bytter til neste type låsing: "grunnlåsing". Den bruker sammenligne-og-bytt-operasjoner (CAS). Dessuten lagrer ikke selve objektoverskriftens merkeord lenger merkeordet, men snarere en referanse til hvor det er lagret, og taggen endres slik at JVM forstår at vi bruker grunnleggende låsing. Hvis flere tråder konkurrerer (strider) om en monitor (en har skaffet seg låsen, og en andre venter på at låsen skal frigjøres), endres taggen i merkeordet, og merkeordet lagrer nå en referanse til monitoren som et objekt - en intern enhet i JVM. Som angitt i JDK Enchancement Proposal (JEP), krever denne situasjonen plass i Native Heap-området i minnet for å lagre denne enheten. Referansen til denne interne enhetens minneplassering vil bli lagret i objektoverskriftens merkeord. Dermed er en skjerm egentlig en mekanisme for å synkronisere tilgang til delte ressurser mellom flere tråder. JVM bytter mellom flere implementeringer av denne mekanismen. Så for enkelhets skyld, når vi snakker om skjermen, snakker vi faktisk om låser. og et sekund venter på at låsen skal frigjøres), så endres taggen i merkeordet, og merkeordet lagrer nå en referanse til monitoren som et objekt - en intern enhet i JVM. Som angitt i JDK Enchancement Proposal (JEP), krever denne situasjonen plass i Native Heap-området i minnet for å lagre denne enheten. Referansen til denne interne enhetens minneplassering vil bli lagret i objektoverskriftens merkeord. Dermed er en skjerm egentlig en mekanisme for å synkronisere tilgang til delte ressurser mellom flere tråder. JVM bytter mellom flere implementeringer av denne mekanismen. Så for enkelhets skyld, når vi snakker om skjermen, snakker vi faktisk om låser. og et sekund venter på at låsen skal frigjøres), så endres taggen i merkeordet, og merkeordet lagrer nå en referanse til monitoren som et objekt - en intern enhet i JVM. Som angitt i JDK Enchancement Proposal (JEP), krever denne situasjonen plass i Native Heap-området i minnet for å lagre denne enheten. Referansen til denne interne enhetens minneplassering vil bli lagret i objektoverskriftens merkeord. Dermed er en skjerm egentlig en mekanisme for å synkronisere tilgang til delte ressurser mellom flere tråder. JVM bytter mellom flere implementeringer av denne mekanismen. Så for enkelhets skyld, når vi snakker om skjermen, snakker vi faktisk om låser. og merkeordet lagrer nå en referanse til monitoren som et objekt - en intern enhet i JVM. Som angitt i JDK Enchancement Proposal (JEP), krever denne situasjonen plass i Native Heap-området i minnet for å lagre denne enheten. Referansen til denne interne enhetens minneplassering vil bli lagret i objektoverskriftens merkeord. Dermed er en skjerm egentlig en mekanisme for å synkronisere tilgang til delte ressurser mellom flere tråder. JVM bytter mellom flere implementeringer av denne mekanismen. Så for enkelhets skyld, når vi snakker om skjermen, snakker vi faktisk om låser. og merkeordet lagrer nå en referanse til monitoren som et objekt - en intern enhet i JVM. Som angitt i JDK Enchancement Proposal (JEP), krever denne situasjonen plass i Native Heap-området i minnet for å lagre denne enheten. Referansen til denne interne enhetens minneplassering vil bli lagret i objektoverskriftens merkeord. Dermed er en skjerm egentlig en mekanisme for å synkronisere tilgang til delte ressurser mellom flere tråder. JVM bytter mellom flere implementeringer av denne mekanismen. Så for enkelhets skyld, når vi snakker om skjermen, snakker vi faktisk om låser. Referansen til denne interne enhetens minneplassering vil bli lagret i objektoverskriftens merkeord. Dermed er en skjerm egentlig en mekanisme for å synkronisere tilgang til delte ressurser mellom flere tråder. JVM bytter mellom flere implementeringer av denne mekanismen. Så for enkelhets skyld, når vi snakker om skjermen, snakker vi faktisk om låser. Referansen til denne interne enhetens minneplassering vil bli lagret i objektoverskriftens merkeord. Dermed er en skjerm egentlig en mekanisme for å synkronisere tilgang til delte ressurser mellom flere tråder. JVM bytter mellom flere implementeringer av denne mekanismen. Så for enkelhets skyld, når vi snakker om skjermen, snakker vi faktisk om låser. Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 7

Synkronisert (venter på lås)

Som vi så tidligere, er konseptet med en "synkronisert blokk" (eller "kritisk seksjon") nært knyttet til konseptet med en skjerm. Ta en titt på et eksempel:

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(" ...");
	}
}
Her sender hovedtråden først oppgaveobjektet til den nye tråden, og får deretter låsen umiddelbart og utfører en lang operasjon med den (8 sekunder). Hele denne tiden kan ikke oppgaven fortsette, fordi den ikke kan gå inn i synchronizedblokken, fordi låsen allerede er anskaffet. Hvis tråden ikke får låsen, vil den vente på skjermen. Så snart den får låsen, vil den fortsette utførelse. Når en tråd går ut av en monitor, frigjør den låsen. I JVisualVM ser det slik ut: Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 8Som du kan se i JVisualVM er statusen "Monitor", noe som betyr at tråden er blokkert og ikke kan ta skjermen. Du kan også bruke kode for å bestemme statusen til en tråd, men navnene på statusen som er bestemt på denne måten samsvarer ikke med navnene som brukes i JVisualVM, selv om de er like. I dette tilfelletth1.getState()setningen i for-løkken vil returnere BLOCKED , fordi så lenge løkken kjører, locker objektets monitor okkupert av maintråden, og th1tråden er blokkert og kan ikke fortsette før låsen er frigjort. I tillegg til synkroniserte blokker kan en hel metode synkroniseres. For eksempel, her er en metode fra HashTableklassen:

public synchronized int size() {
	return count;
}
Denne metoden vil bli utført av bare én tråd til enhver tid. Trenger vi virkelig låsen? Ja, vi trenger det. Når det gjelder instansmetoder, fungerer "dette" objektet (gjeldende objekt) som en lås. Det er en interessant diskusjon om dette emnet her: Er det en fordel å bruke en synkronisert metode i stedet for en synkronisert blokk? . Hvis metoden er statisk, vil ikke låsen være "dette"-objektet (fordi det ikke er noe "dette"-objekt for en statisk metode), men heller et klasseobjekt (for eksempel ) Integer.class.

Vent (venter på en skjerm). notify() og notifyAll() metoder

Thread-klassen har en annen ventemetode som er knyttet til en monitor. I motsetning til sleep()og join(), kan denne metoden ikke bare kalles. Dens navn er wait(). Metoden waitkalles på objektet knyttet til monitoren som vi ønsker å vente på. La oss se et eksempel:

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();
	    }
}
I JVisualVM ser det slik ut: Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 10For å forstå hvordan dette fungerer, husk at metodene wait()og notify()er assosiert med java.lang.Object. Det kan virke rart at trådrelaterte metoder er i Objectklassen. Men årsaken til det utfolder seg nå. Du vil huske at hvert objekt i Java har en header. Overskriften inneholder diverse husholdningsinformasjon, inkludert informasjon om monitoren, dvs. status på låsen. Husk at hvert objekt, eller forekomst av en klasse, er assosiert med en intern enhet i JVM, kalt en indre lås eller monitor. I eksemplet ovenfor indikerer koden for oppgaveobjektet at vi legger inn den synkroniserte blokken for monitoren knyttet til objektet lock. Hvis vi lykkes med å skaffe låsen til denne skjermen, dawait()er kalt. Tråden som utfører oppgaven vil frigjøre lockobjektets monitor, men vil gå inn i køen av tråder som venter på varsling fra objektets lockmonitor. Denne køen av tråder kalles et WAIT SET, som mer korrekt gjenspeiler formålet. Det vil si at det er mer et sett enn en kø. Tråden mainoppretter en ny tråd med oppgaveobjektet, starter den og venter i 3 sekunder. Dette gjør det høyst sannsynlig at den nye tråden vil kunne skaffe seg låsen før tråden main, og komme inn i monitorens kø. Etter det maingår selve tråden inn i lockobjektets synkroniserte blokk og utfører trådvarsling ved hjelp av monitoren. Etter at varselet er sendt, mainfrigir trådenlockobjektets monitor, og den nye tråden, som tidligere ventet på at lockobjektets monitor skulle bli utgitt, fortsetter kjøringen. Det er mulig å sende et varsel til kun én tråd ( notify()) eller samtidig til alle tråder i køen ( notifyAll()). Les mer her: Forskjellen mellom notify() og notifyAll() i Java . Det er viktig å merke seg at varslingsrekkefølgen avhenger av hvordan JVM er implementert. Les mer her: Hvordan løser man sult med notify and notifyAll? . Synkronisering kan utføres uten å spesifisere et objekt. Du kan gjøre dette når en hel metode er synkronisert i stedet for en enkelt kodeblokk. For statiske metoder vil for eksempel låsen være et klasseobjekt (hentet via .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Når det gjelder bruk av låser, er begge metodene de samme. Hvis en metode ikke er statisk, vil synkronisering utføres ved å bruke gjeldende instance, det vil si ved å bruke this. Forresten, vi sa tidligere at du kan bruke getState()metoden for å få statusen til en tråd. For eksempel, for en tråd i køen som venter på en monitor, vil statusen være WAITING eller TIMED_WAITING, hvis metoden wait()spesifiserte et tidsavbrudd. Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

Trådens livssyklus

I løpet av livet endres en tråds status. Faktisk utgjør disse endringene trådens livssyklus. Så snart en tråd er opprettet, er statusen NY. I denne tilstanden kjører ikke den nye tråden ennå, og Java-trådplanleggeren vet ennå ikke noe om den. For at trådplanleggeren skal lære om tråden, må du kalle metoden thread.start(). Deretter vil tråden gå over til KJØRBAR-tilstanden. Internett har mange ukorrekte diagrammer som skiller mellom "Runnable" og "Running" tilstander. Men dette er en feil, fordi Java ikke skiller mellom "klar til å jobbe" (kjørbar) og "arbeid" (løper). Når en tråd er levende, men ikke aktiv (ikke kjørbar), er den i en av to tilstander:
  • BLOKKERT — venter på å gå inn i en kritisk seksjon, dvs. en synchronizedblokk.
  • VENTER — venter på at en annen tråd skal tilfredsstille en eller annen betingelse.
Hvis betingelsen er oppfylt, starter trådplanleggeren tråden. Hvis tråden venter opptil et spesifisert tidspunkt, er statusen TIMED_WAITING. Hvis tråden ikke lenger kjører (den er ferdig eller et unntak ble kastet), går den inn i AVSLUTTET-statusen. For å finne ut en tråds tilstand, bruk getState()metoden. Tråder har også en isAlive()metode, som returnerer sann hvis tråden ikke avsluttes.

LockStøtte og trådparkering

Fra og med Java 1.6 dukket det opp en interessant mekanisme kalt LockSupport . Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 12Denne klassen knytter en "tillatelse" til hver tråd som bruker den. Et kall til park()metoden returnerer umiddelbart hvis tillatelsen er tilgjengelig, og forbruker tillatelsen i prosessen. Ellers blokkerer det. Å kalle unparkmetoden gjør tillatelsen tilgjengelig dersom den ennå ikke er tilgjengelig. Det er kun 1 tillatelse. Java-dokumentasjonen for LockSupportrefererer til Semaphoreklassen. La oss se på et enkelt eksempel:

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!");
    }
}
Denne koden vil alltid vente, for nå har semaforen 0 tillatelser. Og når acquire()det kalles inn koden (dvs. be om tillatelsen), venter tråden til den mottar tillatelsen. Siden vi venter, må vi håndtere InterruptedException. Interessant nok får semaforen en egen trådtilstand. Hvis vi ser i JVisualVM, vil vi se at staten ikke er «Vent», men «Park». Bedre sammen: Java og Thread-klassen.  Del II – Synkronisering – 13La oss se på et annet eksempel:

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);
}
Trådens status vil være WAITING, men JVisualVM skiller mellom waitfra synchronizednøkkelordet og parkfra LockSupportklassen. Hvorfor er dette LockSupportså viktig? Vi går igjen til Java-dokumentasjonen og ser på WAITING- trådtilstanden. Som du kan se, er det bare tre måter å komme inn i det på. To av disse måtene er wait()og join(). Og den tredje er LockSupport. I Java kan låser også bygges på LockSupport og tilby verktøy på høyere nivå. La oss prøve å bruke en. Ta for eksempel en titt på 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();
    }
}
Akkurat som i de foregående eksemplene er alt enkelt her. Objektet lockventer på at noen skal frigi den delte ressursen. Hvis vi ser i JVisualVM, vil vi se at den nye tråden vil bli parkert til tråden mainslipper låsen til den. Du kan lese mer om låser her: Java 8 StampedLocks vs. ReadWriteLocks og Synchronized and Lock API i Java. For bedre å forstå hvordan låser implementeres, er det nyttig å lese om Phaser i denne artikkelen: Veiledning til Java Phaser . Og når du snakker om forskjellige synkroniseringsenheter, må du lese DZone- artikkelen om The Java Synchronizers.

Konklusjon

I denne anmeldelsen undersøkte vi de viktigste måtene tråder samhandler på i Java. Tilleggsmateriale: Bedre sammen: Java og Thread-klassen. Del I — Tråder av utførelse Bedre sammen: Java og trådklassen. Del III — Interaksjon Bedre sammen: Java og Thread-klassen. Del IV — Callable, Future og friends Bedre sammen: Java og Thread-klassen. Del V — Executor, ThreadPool, Fork/Join Better together: Java og Thread-klassen. Del VI – Fyr vekk!
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION