CodeGym /Java blog /Tilfældig /Bedre sammen: Java og Tråd-klassen. Del II — Synkroniseri...
John Squirrels
Niveau
San Francisco

Bedre sammen: Java og Tråd-klassen. Del II — Synkronisering

Udgivet i gruppen

Introduktion

Så vi ved, at Java har tråde. Det kan du læse om i anmeldelsen med titlen Better together: Java and the Thread class. Del I — Udførelsestråde . Tråde er nødvendige for at udføre arbejde parallelt. Dette gør det meget sandsynligt, at trådene på en eller anden måde vil interagere med hinanden. Lad os se på, hvordan dette sker, og hvilke grundlæggende værktøjer vi har. Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 1

Udbytte

Thread.yield() er forvirrende og bruges sjældent. Det er beskrevet på mange forskellige måder på internettet. Herunder nogle mennesker, der skriver, at der er en eller anden kø af tråde, hvor en tråd vil falde ned baseret på trådprioriteter. Andre skriver, at en tråd vil ændre sin status fra "Running" til "Runnable" (selvom der ikke er nogen forskel mellem disse statusser, dvs. Java skelner ikke mellem dem). Virkeligheden er, at det hele er meget mindre kendt og alligevel enklere i en vis forstand. Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 2Der er en fejl ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) logget til yield()metodens dokumentation. Hvis du læser det, er det tydeligt, atyield()metoden giver faktisk kun en anbefaling til Java-trådplanlæggeren om, at denne tråd kan få mindre eksekveringstid. Men hvad der rent faktisk sker, altså om planlæggeren handler efter anbefalingen, og hvad den gør generelt, afhænger af JVM'ens implementering og operativsystemet. Og det kan også afhænge af nogle andre faktorer. Al forvirringen skyldes højst sandsynligt, at multithreading er blevet nytænket, efterhånden som Java-sproget har udviklet sig. Læs mere i oversigten her: Kort introduktion til Java Thread.yield() .

Søvn

En tråd kan gå i dvale under dens udførelse. Dette er den nemmeste form for interaktion med andre tråde. Operativsystemet, der kører den virtuelle Java-maskine, der kører vores Java-kode, har sin egen trådplanlægger . Det bestemmer hvilken tråd der skal startes og hvornår. En programmør kan ikke interagere med denne skemalægger direkte fra Java-kode, kun gennem JVM. Han eller hun kan bede planlæggeren om at sætte tråden på pause et stykke tid, dvs. at sætte den i dvale. Du kan læse mere i disse artikler: Thread.sleep() og How Multithreading works . Du kan også tjekke, hvordan tråde fungerer i Windows-operativsystemer: Internals af Windows-tråd . Og lad os nu se det med vores egne øjne. Gem følgende kode i en fil med navnet 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 eller anden opgave, der venter i 60 sekunder, hvorefter programmet slutter. Vi kompilerer ved hjælp af kommandoen " javac HelloWorldApp.java" og kører derefter programmet ved hjælp af " java HelloWorldApp". Det er bedst at starte programmet i et separat vindue. For eksempel på Windows er det sådan her: start java HelloWorldApp. Vi bruger kommandoen jps til at få PID (proces ID), og vi åbner listen over tråde med " jvisualvm --openpid pid: Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 3Som du kan se, har vores tråd nu statussen "Sovende". Faktisk er der en mere elegant måde at hjælpe på. vores tråd har søde drømme:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Har du bemærket, at vi håndterer InterruptedExceptionoveralt? Lad os forstå hvorfor.

Thread.interrupt()

Sagen er den, at mens en tråd venter/sover, vil nogen måske afbryde. I dette tilfælde håndterer vi en InterruptedException. Denne mekanisme blev oprettet efter Thread.stop()metoden blev erklæret forældet, dvs. forældet og uønsket. Årsagen var, at når stop()metoden blev kaldt, blev tråden simpelthen "slået ihjel", hvilket var meget uforudsigeligt. Vi kunne ikke vide, hvornår tråden ville blive stoppet, og vi kunne ikke garantere datakonsistens. Forestil dig, at du skriver data til en fil, mens tråden er dræbt. I stedet for at dræbe tråden besluttede Javas skabere, at det ville være mere logisk at fortælle den, at den skulle afbrydes. Hvordan man reagerer på denne information er op til tråden selv at afgøre. For flere detaljer, læs Hvorfor er Thread.stop udfaset?på Oracles hjemmeside. Lad os 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 eksempel venter vi ikke 60 sekunder. I stedet vil vi straks vise "Afbrudt". Dette er fordi vi kaldte interrupt()metoden på tråden. Denne metode sætter et internt flag kaldet "afbrydelsesstatus". Det vil sige, at hver tråd har et internt flag, der ikke er direkte tilgængeligt. Men vi har indfødte metoder til at interagere med dette flag. Men det er ikke den eneste måde. En tråd kan køre, ikke venter på noget, blot udfører handlinger. Men den kan forvente, at andre vil ønske at afslutte sit arbejde 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 blive udført, indtil tråden afbrydes eksternt. Hvad angår isInterruptedflaget, er det vigtigt at vide, at hvis vi fanger en InterruptedException, bliver flaget isInterrupted nulstillet, og det isInterrupted()vil derefter returnere falsk. Thread-klassen har også en statisk Thread.interrupted()- metode, der kun gælder for den aktuelle tråd, men denne metode nulstiller flaget til false! Læs mere i dette kapitel med titlen Trådafbrydelse .

Deltag (vent til en anden tråd er færdig)

Den enkleste form for ventetid er at vente på, at endnu en tråd slutter.

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 eksempel vil den nye tråd sove 5 sekunder. Samtidig vil hovedtråden vente, indtil den sovende tråd vågner og afslutter sit arbejde. Hvis du ser på trådens tilstand i JVisualVM, så vil det se sådan ud: Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 4Takket være overvågningsværktøjer kan du se, hvad der foregår med tråden. Metoden joiner ret simpel, fordi det blot er en metode med Java-kode, der udføres, wait()så længe tråden, den kaldes på, er i live. Så snart tråden dør (når den er færdig med sit arbejde), afbrydes ventetiden. Og det er alt det magiske ved metoden join(). Så lad os gå videre til det mest interessante.

Overvåge

Multithreading omfatter konceptet med en skærm. Ordet monitor kommer til engelsk ved hjælp af latin fra det 16. århundrede og betyder "et instrument eller en enhed, der bruges til at observere, kontrollere eller holde en løbende registrering af en proces". I forbindelse med denne artikel vil vi forsøge at dække det grundlæggende. For alle, der ønsker detaljerne, bedes du dykke ned i de linkede materialer. Vi begynder vores rejse med Java Language Specification (JLS): 17.1. Synkronisering . Der står følgende: Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 5Det viser sig, at Java bruger en "monitor"-mekanisme til synkronisering mellem tråde. En skærm er knyttet til hvert objekt, og tråde kan erhverve det med lock()eller frigive det med unlock(). Dernæst finder vi selvstudiet på Oracle-webstedet: Intrinsic Locks and Synchronization. Denne vejledning siger, at Javas synkronisering er bygget op omkring en intern enhed kaldet en intrinsic lock eller monitor lock . Denne lås kaldes ofte blot en " monitor ". Vi ser også igen, at hvert objekt i Java har en iboende lås forbundet med sig. Du kan læse Java - Intrinsic Locks and Synchronization . Dernæst vil det være vigtigt at forstå, hvordan et objekt i Java kan associeres med en skærm. I Java har hvert objekt en header, som gemmer interne metadata, som ikke er tilgængelige for programmøren fra koden, men som den virtuelle maskine skal bruge for at arbejde korrekt med objekter. Objektoverskriften indeholder et "markeringsord", som ser sådan ud: Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 6

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

Her er en JavaWorld-artikel, der er meget nyttig: Hvordan den virtuelle Java-maskine udfører trådsynkronisering . Denne artikel bør kombineres med beskrivelsen fra afsnittet "Oversigt" af følgende udgave i JDK-fejlsporingssystemet: JDK-8183909 . Du kan læse det samme her: JEP-8183909 . Så i Java er en skærm forbundet med et objekt og bruges til at blokere en tråd, når tråden forsøger at erhverve (eller få) låsen. Her er det enkleste eksempel:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Her bruger den aktuelle tråd (den, som disse kodelinjer udføres på) nøgleordet synchronizedtil at forsøge at bruge den monitor, der er forbundet medobject"\variabel for at få/erhverve låsen. Hvis ingen andre kæmper om skærmen (dvs. ingen andre kører synkroniseret kode ved hjælp af det samme objekt), så kan Java forsøge at udføre en optimering kaldet "biased locking". Et relevant tag og en post om, hvilken tråd der ejer monitorens lås, tilføjes til mærkeordet i objekthovedet. Dette reducerer det overhead, der kræves for at låse en skærm. Hvis skærmen tidligere var ejet af en anden tråd, er en sådan låsning ikke nok. JVM'en skifter til næste type låsning: "basislåsning". Den bruger sammenligne-og-byt-operationer (CAS). Desuden gemmer selve objekthovedets mærkeord ikke længere mærkeordet, men derimod en henvisning til, hvor det er gemt, og tagget ændres, så JVM'en forstår, at vi bruger grundlæggende låsning. Hvis flere tråde konkurrerer (strides) om en skærm (en har erhvervet låsen, og en anden venter på, at låsen udløses), så ændres mærket i mærkeordet, og mærkeordet gemmer nu en reference til skærmen som et objekt — en intern enhed i JVM. Som angivet i JDK Enchancement Proposal (JEP), kræver denne situation plads i hukommelsesområdet Native Heap for at gemme denne enhed. Referencen til denne interne enheds hukommelsesplacering vil blive gemt i objekthovedets mærkeord. Således er en skærm virkelig en mekanisme til at synkronisere adgang til delte ressourcer mellem flere tråde. JVM'en skifter mellem flere implementeringer af denne mekanisme. Så for nemheds skyld, når vi taler om skærmen, taler vi faktisk om låse. og et sekund venter på, at låsen udløses), så ændres mærket i mærkeordet, og mærkeordet gemmer nu en reference til monitoren som et objekt - en intern enhed i JVM. Som angivet i JDK Enchancement Proposal (JEP), kræver denne situation plads i hukommelsesområdet Native Heap for at gemme denne enhed. Referencen til denne interne enheds hukommelsesplacering vil blive gemt i objekthovedets mærkeord. Således er en skærm virkelig en mekanisme til at synkronisere adgang til delte ressourcer mellem flere tråde. JVM'en skifter mellem flere implementeringer af denne mekanisme. Så for nemheds skyld, når vi taler om skærmen, taler vi faktisk om låse. og et sekund venter på, at låsen udløses), så ændres mærket i mærkeordet, og mærkeordet gemmer nu en reference til monitoren som et objekt - en intern enhed i JVM. Som angivet i JDK Enchancement Proposal (JEP), kræver denne situation plads i hukommelsesområdet Native Heap for at gemme denne enhed. Referencen til denne interne enheds hukommelsesplacering vil blive gemt i objekthovedets mærkeord. Således er en skærm virkelig en mekanisme til at synkronisere adgang til delte ressourcer mellem flere tråde. JVM'en skifter mellem flere implementeringer af denne mekanisme. Så for nemheds skyld, når vi taler om skærmen, taler vi faktisk om låse. og mærkeordet gemmer nu en reference til monitoren som et objekt - en intern enhed i JVM. Som angivet i JDK Enchancement Proposal (JEP), kræver denne situation plads i hukommelsesområdet Native Heap for at gemme denne enhed. Referencen til denne interne enheds hukommelsesplacering vil blive gemt i objekthovedets mærkeord. Således er en skærm virkelig en mekanisme til at synkronisere adgang til delte ressourcer mellem flere tråde. JVM'en skifter mellem flere implementeringer af denne mekanisme. Så for nemheds skyld, når vi taler om skærmen, taler vi faktisk om låse. og mærkeordet gemmer nu en reference til monitoren som et objekt - en intern enhed i JVM. Som angivet i JDK Enchancement Proposal (JEP), kræver denne situation plads i hukommelsesområdet Native Heap for at gemme denne enhed. Referencen til denne interne enheds hukommelsesplacering vil blive gemt i objekthovedets mærkeord. Således er en skærm virkelig en mekanisme til at synkronisere adgang til delte ressourcer mellem flere tråde. JVM'en skifter mellem flere implementeringer af denne mekanisme. Så for nemheds skyld, når vi taler om skærmen, taler vi faktisk om låse. Referencen til denne interne enheds hukommelsesplacering vil blive gemt i objekthovedets mærkeord. Således er en skærm virkelig en mekanisme til at synkronisere adgang til delte ressourcer mellem flere tråde. JVM'en skifter mellem flere implementeringer af denne mekanisme. Så for nemheds skyld, når vi taler om skærmen, taler vi faktisk om låse. Referencen til denne interne enheds hukommelsesplacering vil blive gemt i objekthovedets mærkeord. Således er en skærm virkelig en mekanisme til at synkronisere adgang til delte ressourcer mellem flere tråde. JVM'en skifter mellem flere implementeringer af denne mekanisme. Så for nemheds skyld, når vi taler om skærmen, taler vi faktisk om låse. Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 7

Synkroniseret (venter på en lås)

Som vi så tidligere, er begrebet "synkroniseret blok" (eller "kritisk sektion") tæt forbundet med begrebet en skærm. Tag et kig 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 opgaveobjektet til den nye tråd, og erhverver derefter straks låsen og udfører en lang operation med den (8 sekunder). Al denne tid kan opgaven ikke fortsætte, fordi den ikke kan komme ind i synchronizedblokken, fordi låsen allerede er erhvervet. Hvis tråden ikke kan få låsen, vil den vente på monitoren. Så snart den får låsen, vil den fortsætte med at udføre. Når en tråd forlader en skærm, udløser den låsen. I JVisualVM ser det sådan ud: Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 8Som du kan se i JVisualVM er status "Monitor", hvilket betyder at tråden er blokeret og ikke kan tage monitoren. Du kan også bruge kode til at bestemme en tråds status, men navnene på status, der bestemmes på denne måde, stemmer ikke overens med navnene, der bruges i JVisualVM, selvom de ligner hinanden. I dette tilfældeth1.getState()sætningen i for-løkken vil returnere BLOCKED , fordi så længe løkken kører, locker objektets skærm optaget af tråden main, og th1tråden er blokeret og kan ikke fortsætte før låsen er frigivet. Ud over synkroniserede blokke kan en hel metode synkroniseres. For eksempel, her er en metode fra HashTableklassen:

public synchronized int size() {
	return count;
}
Denne metode vil kun blive udført af én tråd på et givet tidspunkt. Har vi virkelig brug for låsen? Ja, vi har brug for det. I tilfælde af instansmetoder fungerer "dette" objektet (det nuværende objekt) som en lås. Der er en interessant diskussion om dette emne her: Er der en fordel ved at bruge en synkroniseret metode i stedet for en synkroniseret blok? . Hvis metoden er statisk, så vil låsen ikke være "dette" objektet (fordi der ikke er noget "dette" objekt for en statisk metode), men snarere et klasseobjekt (f.eks. ) Integer.class.

Vent (venter på en skærm). notify() og notifyAll() metoder

Thread-klassen har en anden ventemetode, der er knyttet til en skærm. I modsætning til sleep()og join()kan denne metode ikke blot kaldes. Dens navn er wait(). Metoden waitkaldes på det objekt, der er knyttet til skærmen, som vi vil vente på. Lad os 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 sådan ud: Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 10For at forstå, hvordan dette virker, skal du huske, at metoderne wait()og notify()er forbundet med java.lang.Object. Det kan virke mærkeligt, at tråd-relaterede metoder er i Objectklassen. Men grunden til det udfolder sig nu. Du vil huske, at hvert objekt i Java har en header. Overskriften indeholder forskellige husholdningsoplysninger, herunder oplysninger om monitoren, dvs. låsens status. Husk, at hvert objekt eller forekomst af en klasse er knyttet til en intern enhed i JVM, kaldet en indre lås eller monitor. I eksemplet ovenfor angiver koden for opgaveobjektet, at vi indtaster den synkroniserede blok for den monitor, der er knyttet til objektet lock. Hvis det lykkes os at anskaffe låsen til denne skærm, såwait()Hedder. Tråden, der udfører opgaven, frigiver lockobjektets skærm, men kommer ind i køen af ​​tråde, der venter på besked fra objektets lockskærm. Denne kø af tråde kaldes et WAIT SET, som mere korrekt afspejler dens formål. Det vil sige, det er mere et sæt end en kø. Tråden mainopretter en ny tråd med opgaveobjektet, starter den og venter i 3 sekunder. Dette gør det meget sandsynligt, at den nye tråd vil være i stand til at erhverve låsen før tråden mainog komme ind i monitorens kø. Derefter maingår tråden selv ind i lockobjektets synkroniserede blok og udfører trådmeddelelse ved hjælp af monitoren. Efter meddelelsen er sendt, mainfrigiver trådenlockobjektets skærm, og den nye tråd, som tidligere ventede på, at lockobjektets skærm blev frigivet, fortsætter eksekveringen. Det er muligt at sende en notifikation til kun én tråd ( notify()) eller samtidigt til alle tråde i køen ( notifyAll()). Læs mere her: Forskellen mellem notify() og notifyAll() i Java . Det er vigtigt at bemærke, at meddelelsesrækkefølgen afhænger af, hvordan JVM er implementeret. Læs mere her: Sådan løser du sult med notify og notifyAll? . Synkronisering kan udføres uden at angive et objekt. Du kan gøre dette, når en hel metode er synkroniseret i stedet for en enkelt kodeblok. For statiske metoder vil låsen f.eks. være et klasseobjekt (opnået via .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Med hensyn til brug af låse er begge metoder de samme. Hvis en metode ikke er statisk, vil synkroniseringen blive udført ved hjælp af den aktuelle instance, det vil sige ved hjælp af this. Forresten sagde vi tidligere, at du kan bruge getState()metoden til at få status for en tråd. For en tråd i køen, der f.eks. venter på en monitor, vil status være WAITING eller TIMED_WAITING, hvis metoden har wait()angivet en timeout. Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 11

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

Trådens livscyklus

I løbet af dens liv ændres en tråds status. Faktisk omfatter disse ændringer trådens livscyklus. Så snart en tråd er oprettet, er dens status NY. I denne tilstand kører den nye tråd endnu ikke, og Java-trådplanlæggeren ved endnu ikke noget om det. For at trådplanlæggeren kan lære om tråden, skal du kalde thread.start()metoden. Derefter vil tråden gå over til tilstanden RUNNABLE. Internettet har masser af forkerte diagrammer, der skelner mellem "Kørbar" og "Kører" tilstande. Men dette er en fejl, fordi Java ikke skelner mellem "klar til at arbejde" (kørbar) og "arbejde" (kørende). Når en tråd er i live, men ikke aktiv (ikke kan køres), er den i en af ​​to tilstande:
  • BLOKERET — venter på at komme ind i en kritisk sektion, dvs. en synchronizedblok.
  • WAITING — venter på, at en anden tråd opfylder en eller anden betingelse.
Hvis betingelsen er opfyldt, starter trådplanlæggeren tråden. Hvis tråden venter op til et bestemt tidspunkt, er dens status TIMED_WAITING. Hvis tråden ikke længere kører (den er afsluttet, eller en undtagelse blev smidt), så går den i status AFSLUTET. Brug metoden for at finde ud af en tråds tilstand getState(). Tråde har også en isAlive()metode, som returnerer sand, hvis tråden ikke afsluttes.

LockSupport og trådparkering

Begyndende med Java 1.6 dukkede en interessant mekanisme kaldet LockSupport op. Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 12Denne klasse knytter en "tilladelse" til hver tråd, der bruger den. Et kald til park()metoden vender straks tilbage, hvis tilladelsen er tilgængelig, og forbruge tilladelsen i processen. Ellers blokerer den. Kaldning af unparkmetoden gør tilladelsen tilgængelig, hvis den endnu ikke er tilgængelig. Der er kun 1 tilladelse. Java-dokumentationen for LockSupporthenviser til Semaphoreklassen. Lad os se på et simpelt 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 kode vil altid vente, for nu har semaforen 0 tilladelser. Og når acquire()der kaldes i koden (dvs. anmode om tilladelsen), venter tråden indtil den modtager tilladelsen. Da vi venter, må vi håndtere InterruptedException. Interessant nok får semaforen en separat trådtilstand. Hvis vi kigger i JVisualVM, vil vi se, at tilstanden ikke er "Vent", men "Park". Bedre sammen: Java og Tråd-klassen.  Del II — Synkronisering - 13Lad os se på et andet 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 skelner mellem waitfra synchronizednøgleordet og parkfra LockSupportklassen. Hvorfor er dette LockSupportså vigtigt? Vi vender igen til Java-dokumentationen og ser på WAITING- trådtilstanden. Som du kan se, er der kun tre måder at komme ind i det på. To af disse måder er wait()og join(). Og den tredje er LockSupport. I Java kan låse også bygges på LockSupport og tilbyde værktøjer på højere niveau. Lad os prøve at bruge en. Tag for eksempel et kig 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();
    }
}
Ligesom i de foregående eksempler er alt enkelt her. Objektet lockventer på, at nogen frigiver den delte ressource. Hvis vi kigger i JVisualVM, vil vi se, at den nye tråd vil blive parkeret, indtil tråden mainfrigiver låsen til den. Du kan læse mere om låse her: Java 8 StampedLocks vs. ReadWriteLocks og Synchronized and Lock API i Java. For bedre at forstå, hvordan låse implementeres, er det nyttigt at læse om Phaser i denne artikel: Guide til Java Phaser . Og når vi taler om forskellige synkroniseringsapparater, skal du læse DZone- artiklen om Java-synkroniseringerne.

Konklusion

I denne anmeldelse undersøgte vi de vigtigste måder, hvorpå tråde interagerer i Java. Yderligere materiale: Bedre sammen: Java og Tråd-klassen. Del I — Udførelsestråde Bedre sammen: Java og trådklassen. Del III — Interaktion 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 sammen: Java og Thread-klassen. Del VI - Fyr væk!
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION