CodeGym /Java Blog /Random-IT /Meglio insieme: Java e la classe Thread. Parte II — Sincr...
John Squirrels
Livello 41
San Francisco

Meglio insieme: Java e la classe Thread. Parte II — Sincronizzazione

Pubblicato nel gruppo Random-IT

introduzione

Quindi, sappiamo che Java ha thread. Puoi leggerlo nella recensione intitolata Better together: Java and the Thread class. Parte I — Fili di esecuzione . I thread sono necessari per eseguire lavori in parallelo. Ciò rende molto probabile che i thread interagiscano in qualche modo tra loro. Diamo un'occhiata a come ciò accade e quali strumenti di base abbiamo. Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 1

Prodotto

Thread.yield() è sconcertante e usato raramente. È descritto in molti modi diversi su Internet. Comprese alcune persone che scrivono che esiste una coda di thread, in cui un thread scenderà in base alle priorità del thread. Altre persone scrivono che un thread cambierà il suo stato da "Running" a "Eseguibile" (anche se non c'è distinzione tra questi stati, cioè Java non li distingue). La realtà è che è tutto molto meno noto e tuttavia più semplice in un certo senso. Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 2C'è un bug ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) registrato per la yield()documentazione del metodo. Se lo leggi, è chiaro che ilyield()Il metodo in realtà fornisce solo alcune raccomandazioni allo scheduler del thread Java che a questo thread può essere concesso un tempo di esecuzione inferiore. Ma ciò che effettivamente accade, ovvero se lo scheduler agisce in base alla raccomandazione e cosa fa in generale, dipende dall'implementazione della JVM e dal sistema operativo. E potrebbe dipendere anche da altri fattori. Tutta la confusione è molto probabilmente dovuta al fatto che il multithreading è stato ripensato con lo sviluppo del linguaggio Java. Maggiori informazioni nella panoramica qui: Breve introduzione a Java Thread.yield() .

Sonno

Un thread può andare a dormire durante la sua esecuzione. Questo è il tipo più semplice di interazione con altri thread. Il sistema operativo che esegue la macchina virtuale Java che esegue il nostro codice Java ha il proprio programmatore di thread . Decide quale thread iniziare e quando. Un programmatore non può interagire con questo scheduler direttamente dal codice Java, solo attraverso la JVM. Lui o lei può chiedere allo scheduler di mettere in pausa il thread per un po', cioè di metterlo a dormire. Puoi leggere di più in questi articoli: Thread.sleep() e How Multithreading works . Puoi anche controllare come funzionano i thread nei sistemi operativi Windows: Internals of Windows Thread . E ora vediamolo con i nostri occhi. Salva il seguente codice in un file chiamato 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();
    }
}
Come puoi vedere, abbiamo un compito che attende 60 secondi, dopodiché il programma termina. Compiliamo usando il comando " javac HelloWorldApp.java" e quindi eseguiamo il programma usando " java HelloWorldApp". È meglio avviare il programma in una finestra separata. Ad esempio, su Windows, è così: start java HelloWorldApp. Usiamo il comando jps per ottenere il PID (process ID) e apriamo l'elenco dei thread con " jvisualvm --openpid pid: Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 3Come puoi vedere, il nostro thread ora ha lo stato "Sleeping". In effetti, c'è un modo più elegante per aiutare il nostro thread ha sogni d'oro:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Hai notato che gestiamo InterruptedExceptionovunque? Capiamo perché.

Thread.interrupt()

Il fatto è che mentre un thread è in attesa/dormiente, qualcuno potrebbe voler interrompere. In questo caso, gestiamo un file InterruptedException. Questo meccanismo è stato creato dopo che il Thread.stop()metodo è stato dichiarato Deprecato, cioè obsoleto e indesiderabile. Il motivo era che quando stop()veniva chiamato il metodo, il thread veniva semplicemente "ucciso", il che era molto imprevedibile. Non potevamo sapere quando il thread sarebbe stato interrotto e non potevamo garantire la coerenza dei dati. Immagina di scrivere dati su un file mentre il thread viene ucciso. Piuttosto che uccidere il thread, i creatori di Java hanno deciso che sarebbe stato più logico dirgli che doveva essere interrotto. Come rispondere a queste informazioni è una questione che spetta al thread stesso decidere. Per maggiori dettagli, leggi Perché Thread.stop è obsoleto?sul sito Web di Oracle. Diamo un'occhiata a un esempio:

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 questo esempio, non aspetteremo 60 secondi. Al contrario, visualizzeremo immediatamente "Interrotto". Questo perché abbiamo chiamato il interrupt()metodo sul thread. Questo metodo imposta un flag interno chiamato "stato di interruzione". Cioè, ogni thread ha un flag interno che non è direttamente accessibile. Ma abbiamo metodi nativi per interagire con questo flag. Ma non è l'unico modo. Un thread potrebbe essere in esecuzione, non in attesa di qualcosa, semplicemente eseguendo azioni. Ma può anticipare che altri vorranno terminare il suo lavoro in un momento specifico. Per esempio:

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();
}
Nell'esempio precedente, il whileciclo verrà eseguito finché il thread non viene interrotto esternamente. Per quanto riguarda il isInterruptedflag, è importante sapere che se rileviamo un InterruptedException, il flag isInterrupted viene resettato e quindi isInterrupted()restituirà false. La classe Thread ha anche un metodo Thread.interrupted() statico che si applica solo al thread corrente, ma questo metodo reimposta il flag su false! Maggiori informazioni in questo capitolo intitolato Thread Interruption .

Unisciti (Attendi che finisca un altro thread)

Il tipo più semplice di attesa è aspettare che un altro thread finisca.

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 questo esempio, il nuovo thread dormirà per 5 secondi. Allo stesso tempo, il thread principale attenderà fino a quando il thread dormiente non si sveglierà e finirà il suo lavoro. Se guardi lo stato del thread in JVisualVM, sarà simile a questo: Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 4Grazie agli strumenti di monitoraggio, puoi vedere cosa sta succedendo con il thread. Il joinmetodo è piuttosto semplice, perché è solo un metodo con codice Java che viene eseguito wait()finché il thread su cui viene chiamato è attivo. Non appena il filo muore (quando ha terminato il suo lavoro), l'attesa si interrompe. E questa è tutta la magia del join()metodo. Quindi, passiamo alla cosa più interessante.

Tenere sotto controllo

Il multithreading include il concetto di monitor. La parola monitor deriva dall'inglese dal latino del XVI secolo e significa "uno strumento o dispositivo utilizzato per osservare, controllare o tenere una registrazione continua di un processo". Nel contesto di questo articolo, cercheremo di coprire le basi. Per chiunque desideri i dettagli, si prega di immergersi nei materiali collegati. Iniziamo il nostro viaggio con la Java Language Specification (JLS): 17.1. Sincronizzazione . Dice quanto segue: Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 5Si scopre che Java utilizza un meccanismo di "monitor" per la sincronizzazione tra i thread. Un monitor è associato a ciascun oggetto e i thread possono acquisirlo lock()o rilasciarlo con unlock(). Successivamente, troveremo il tutorial sul sito Web di Oracle: Intrinsic Locks and Synchronization. Questo tutorial afferma che la sincronizzazione di Java è costruita attorno a un'entità interna chiamata blocco intrinseco o blocco del monitor . Questo lucchetto è spesso chiamato semplicemente " monitor ". Vediamo anche di nuovo che ogni oggetto in Java ha un blocco intrinseco ad esso associato. Puoi leggere Java - Blocchi intrinseci e sincronizzazione . Successivamente sarà importante capire come un oggetto in Java può essere associato a un monitor. In Java, ogni oggetto ha un'intestazione che memorizza i metadati interni non disponibili al programmatore dal codice, ma di cui la macchina virtuale ha bisogno per lavorare correttamente con gli oggetti. L'intestazione dell'oggetto include una "parola di marca", che ha il seguente aspetto: Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 6

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

Ecco un articolo di JavaWorld molto utile: Come la macchina virtuale Java esegue la sincronizzazione dei thread . Questo articolo deve essere combinato con la descrizione della sezione "Riepilogo" del seguente problema nel sistema di tracciamento dei bug JDK: JDK-8183909 . Puoi leggere la stessa cosa qui: JEP-8183909 . Quindi, in Java, un monitor è associato a un oggetto e viene utilizzato per bloccare un thread quando il thread tenta di acquisire (o ottenere) il blocco. Ecco l'esempio più semplice:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Qui, il thread corrente (quello su cui vengono eseguite queste righe di codice) utilizza la synchronizedparola chiave per tentare di utilizzare il monitor associato alobject"\variabile per ottenere/acquisire il lock. Se nessun altro si sta contendendo il monitor (cioè nessun altro sta eseguendo codice sincronizzato utilizzando lo stesso oggetto), allora Java potrebbe tentare di eseguire un'ottimizzazione chiamata "blocco parziale". Un tag pertinente e un record su quale thread possiede il blocco del monitor vengono aggiunti alla mark word nell'intestazione dell'oggetto. Ciò riduce il sovraccarico necessario per bloccare un monitor. Se il monitor era precedentemente di proprietà di un altro thread, tale blocco non è sufficiente. La JVM passa al tipo successivo di blocco: "blocco di base". Utilizza operazioni di confronto e scambio (CAS). Inoltre, la mark word stessa dell'intestazione dell'oggetto non memorizza più la mark word, ma piuttosto un riferimento a dove è memorizzata e il tag cambia in modo che la JVM capisca che stiamo utilizzando il blocco di base. Se più thread competono (si contendono) per un monitor (uno ha acquisito il blocco e un secondo è in attesa che il blocco venga rilasciato), allora il tag nella parola di marcatura cambia e la parola di marcatura ora memorizza un riferimento al monitor come oggetto — un'entità interna della JVM. Come affermato nella JDK Enchancement Proposal (JEP), questa situazione richiede spazio nell'area di memoria dell'heap nativo per archiviare questa entità. Il riferimento alla posizione di memoria di questa entità interna verrà memorizzato nella mark word dell'intestazione dell'oggetto. Pertanto, un monitor è in realtà un meccanismo per sincronizzare l'accesso alle risorse condivise tra più thread. La JVM passa tra diverse implementazioni di questo meccanismo. Quindi, per semplicità, quando si parla di monitor, in realtà si parla di serrature. e un secondo è in attesa del rilascio del blocco), quindi il tag nella parola di marcatura cambia e la parola di marcatura ora memorizza un riferimento al monitor come oggetto, un'entità interna della JVM. Come affermato nella JDK Enchancement Proposal (JEP), questa situazione richiede spazio nell'area di memoria dell'heap nativo per archiviare questa entità. Il riferimento alla posizione di memoria di questa entità interna verrà memorizzato nella mark word dell'intestazione dell'oggetto. Pertanto, un monitor è in realtà un meccanismo per sincronizzare l'accesso alle risorse condivise tra più thread. La JVM passa tra diverse implementazioni di questo meccanismo. Quindi, per semplicità, quando si parla di monitor, in realtà si parla di serrature. e un secondo è in attesa del rilascio del blocco), quindi il tag nella parola di marcatura cambia e la parola di marcatura ora memorizza un riferimento al monitor come oggetto, un'entità interna della JVM. Come affermato nella JDK Enchancement Proposal (JEP), questa situazione richiede spazio nell'area di memoria dell'heap nativo per archiviare questa entità. Il riferimento alla posizione di memoria di questa entità interna verrà memorizzato nella mark word dell'intestazione dell'oggetto. Pertanto, un monitor è in realtà un meccanismo per sincronizzare l'accesso alle risorse condivise tra più thread. La JVM passa tra diverse implementazioni di questo meccanismo. Quindi, per semplicità, quando si parla di monitor, in realtà si parla di serrature. e la parola chiave ora memorizza un riferimento al monitor come oggetto, un'entità interna della JVM. Come affermato nella JDK Enchancement Proposal (JEP), questa situazione richiede spazio nell'area di memoria dell'heap nativo per archiviare questa entità. Il riferimento alla posizione di memoria di questa entità interna verrà memorizzato nella mark word dell'intestazione dell'oggetto. Pertanto, un monitor è in realtà un meccanismo per sincronizzare l'accesso alle risorse condivise tra più thread. La JVM passa tra diverse implementazioni di questo meccanismo. Quindi, per semplicità, quando si parla di monitor, in realtà si parla di serrature. e la parola chiave ora memorizza un riferimento al monitor come oggetto, un'entità interna della JVM. Come affermato nella JDK Enchancement Proposal (JEP), questa situazione richiede spazio nell'area di memoria dell'heap nativo per archiviare questa entità. Il riferimento alla posizione di memoria di questa entità interna verrà memorizzato nella mark word dell'intestazione dell'oggetto. Pertanto, un monitor è in realtà un meccanismo per sincronizzare l'accesso alle risorse condivise tra più thread. La JVM passa tra diverse implementazioni di questo meccanismo. Quindi, per semplicità, quando si parla di monitor, in realtà si parla di serrature. Il riferimento alla posizione di memoria di questa entità interna verrà memorizzato nella mark word dell'intestazione dell'oggetto. Pertanto, un monitor è in realtà un meccanismo per sincronizzare l'accesso alle risorse condivise tra più thread. La JVM passa tra diverse implementazioni di questo meccanismo. Quindi, per semplicità, quando si parla di monitor, in realtà si parla di serrature. Il riferimento alla posizione di memoria di questa entità interna verrà memorizzato nella mark word dell'intestazione dell'oggetto. Pertanto, un monitor è in realtà un meccanismo per sincronizzare l'accesso alle risorse condivise tra più thread. La JVM passa tra diverse implementazioni di questo meccanismo. Quindi, per semplicità, quando si parla di monitor, in realtà si parla di serrature. Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 7

Sincronizzato (in attesa di un blocco)

Come abbiamo visto in precedenza, il concetto di "blocco sincronizzato" (o "sezione critica") è strettamente correlato al concetto di monitor. Dai un'occhiata a un esempio:

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(" ...");
	}
}
Qui, il thread principale prima passa l'oggetto attività al nuovo thread, quindi acquisisce immediatamente il blocco ed esegue una lunga operazione con esso (8 secondi). Per tutto questo tempo l'attività non è in grado di procedere, perché non può entrare nel synchronizedblocco, perché il blocco è già acquisito. Se il thread non può ottenere il blocco, attenderà il monitor. Non appena ottiene il blocco, continuerà l'esecuzione. Quando un thread esce da un monitor, rilascia il blocco. In JVisualVM, ha questo aspetto: Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 8Come puoi vedere in JVisualVM, lo stato è "Monitor", il che significa che il thread è bloccato e non può prendere il monitor. È inoltre possibile utilizzare il codice per determinare lo stato di un thread, ma i nomi dello stato determinati in questo modo non corrispondono ai nomi utilizzati in JVisualVM, sebbene siano simili. In questo caso, ilth1.getState()L'istruzione for nel ciclo restituirà BLOCKED , perché finché il ciclo è in esecuzione, il lockmonitor dell'oggetto è occupato dal mainthread e il th1thread è bloccato e non può procedere finché il blocco non viene rilasciato. Oltre ai blocchi sincronizzati, è possibile sincronizzare un intero metodo. Ad esempio, ecco un metodo della HashTableclasse:

public synchronized int size() {
	return count;
}
Questo metodo verrà eseguito da un solo thread alla volta. Abbiamo davvero bisogno della serratura? Sì, ne abbiamo bisogno. Nel caso dei metodi di istanza, l'oggetto "this" (oggetto corrente) funge da blocco. C'è una discussione interessante su questo argomento qui: c'è un vantaggio nell'usare un metodo sincronizzato invece di un blocco sincronizzato? . Se il metodo è statico, il blocco non sarà l'oggetto "questo" (perché non esiste un oggetto "questo" per un metodo statico), ma piuttosto un oggetto Class (ad esempio, ) Integer.class.

Aspetta (in attesa di un monitor). i metodi notify() e notifyAll()

La classe Thread dispone di un altro metodo di attesa associato a un monitor. A differenza di sleep()e join(), questo metodo non può essere semplicemente chiamato. Il suo nome è wait(). Il waitmetodo viene chiamato sull'oggetto associato al monitor che vogliamo attendere. Vediamo un esempio:

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, ha questo aspetto: Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 10Per capire come funziona, ricorda che i metodi wait()e notify()sono associati a java.lang.Object. Può sembrare strano che i metodi relativi ai thread siano nella Objectclasse. Ma la ragione di ciò ora si svela. Ricorderai che ogni oggetto in Java ha un'intestazione. L'intestazione contiene varie informazioni di pulizia, comprese informazioni sul monitor, ovvero lo stato della serratura. Ricorda, ogni oggetto, o istanza di una classe, è associato a un'entità interna nella JVM, chiamata blocco intrinseco o monitor. Nell'esempio sopra, il codice per l'oggetto attività indica che entriamo nel blocco sincronizzato per il monitor associato all'oggetto lock. Se riusciamo ad acquisire il blocco per questo monitor, allorawait()è chiamato. Il thread che esegue l'attività rilascerà il lockmonitor dell'oggetto, ma entrerà nella coda dei thread in attesa di notifica dal lockmonitor dell'oggetto. Questa coda di thread è chiamata WAIT SET, che riflette più propriamente il suo scopo. Cioè, è più un set che una coda. Il mainthread crea un nuovo thread con l'oggetto attività, lo avvia e attende 3 secondi. Ciò rende molto probabile che il nuovo thread sia in grado di acquisire il blocco prima del mainthread e di entrare nella coda del monitor. Successivamente, il mainthread stesso entra nel lockblocco sincronizzato dell'oggetto ed esegue la notifica del thread utilizzando il monitor. Dopo l'invio della notifica, il mainthread rilascia il filelockmonitor dell'oggetto e il nuovo thread, che in precedenza era in attesa del lockrilascio del monitor dell'oggetto, continua l'esecuzione. È possibile inviare una notifica a un solo thread ( notify()) o contemporaneamente a tutti i thread in coda ( notifyAll()). Leggi di più qui: Differenza tra notify() e notifyAll() in Java . È importante notare che l'ordine di notifica dipende da come viene implementata la JVM. Leggi di più qui: Come risolvere la fame con notifica e notifica a tutti? . La sincronizzazione può essere eseguita senza specificare un oggetto. Puoi farlo quando viene sincronizzato un intero metodo anziché un singolo blocco di codice. Ad esempio, per i metodi statici, il lock sarà un oggetto Class (ottenuto tramite .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
In termini di utilizzo dei blocchi, entrambi i metodi sono gli stessi. Se un metodo non è statico, la sincronizzazione verrà eseguita utilizzando current instance, ovvero utilizzando this. A proposito, abbiamo detto prima che puoi usare il getState()metodo per ottenere lo stato di un thread. Ad esempio, per un thread nella coda in attesa di un monitor, lo stato sarà WAITING o TIMED_WAITING, se il wait()metodo ha specificato un timeout. Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 11

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

Ciclo di vita del filo

Nel corso della sua vita, lo stato di un thread cambia. In effetti, questi cambiamenti comprendono il ciclo di vita del thread. Non appena viene creato un thread, il suo stato è NUOVO. In questo stato, il nuovo thread non è ancora in esecuzione e lo scheduler dei thread Java non ne sa ancora nulla. Affinché lo scheduler dei thread impari a conoscere il thread, è necessario chiamare il thread.start()metodo. Quindi il thread passerà allo stato RUNNABLE. Internet ha molti diagrammi errati che distinguono tra gli stati "Eseguibile" e "In esecuzione". Ma questo è un errore, perché Java non distingue tra "ready to work" (eseguibile) e "working" (in esecuzione). Quando un thread è attivo ma non attivo (non eseguibile), si trova in uno dei due stati:
  • BLOCCATO — in attesa di entrare in una sezione critica, cioè un synchronizedblocco.
  • WAITING — in attesa che un altro thread soddisfi alcune condizioni.
Se la condizione è soddisfatta, lo scheduler del thread avvia il thread. Se il thread è in attesa fino a un tempo specificato, il suo stato è TIMED_WAITING. Se il thread non è più in esecuzione (è terminato o è stata generata un'eccezione), entra nello stato TERMINATED. Per scoprire lo stato di un thread, utilizzare il getState()metodo. I thread hanno anche un isAlive()metodo, che restituisce true se il thread non è TERMINATED.

LockSupport e parcheggio thread

A partire da Java 1.6, è apparso un meccanismo interessante chiamato LockSupport . Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 12Questa classe associa un "permesso" a ogni thread che lo utilizza. Una chiamata al park()metodo viene restituita immediatamente se il permesso è disponibile, consumando il permesso nel processo. Altrimenti si blocca. La chiamata al unparkmetodo rende disponibile il permesso se non è ancora disponibile. C'è solo 1 permesso. La documentazione Java per LockSupportfa riferimento alla Semaphoreclasse. Diamo un'occhiata a un semplice esempio:

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!");
    }
}
Questo codice aspetterà sempre, perché ora il semaforo ha 0 permessi. E quando acquire()viene chiamato nel codice (cioè richiede il permesso), il thread attende finché non riceve il permesso. Dal momento che stiamo aspettando, dobbiamo gestire InterruptedException. È interessante notare che il semaforo ottiene uno stato di thread separato. Se guardiamo in JVisualVM, vedremo che lo stato non è "Wait", ma "Park". Meglio insieme: Java e la classe Thread.  Parte II - Sincronizzazione - 13Diamo un'occhiata a un altro esempio:

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);
}
Lo stato del thread sarà WAITING, ma JVisualVM distingue tra waitdalla synchronizedparola chiave e parkdalla LockSupportclasse. Perché è LockSupportcosì importante? Torniamo alla documentazione Java e osserviamo lo stato del thread WAITING . Come puoi vedere, ci sono solo tre modi per entrarci. Due di questi modi sono wait()e join(). E il terzo è LockSupport. In Java, i blocchi possono anche essere costruiti su LockSupport e offrire strumenti di livello superiore. Proviamo a usarne uno. Ad esempio, dai un'occhiata a 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();
    }
}
Proprio come negli esempi precedenti, qui tutto è semplice. L' lockoggetto attende che qualcuno rilasci la risorsa condivisa. Se guardiamo in JVisualVM, vedremo che il nuovo thread verrà parcheggiato fino a quando il mainthread non rilascerà il blocco. Puoi leggere ulteriori informazioni sui blocchi qui: Java 8 StampedLocks vs. ReadWriteLocks e Synchronized and Lock API in Java. Per comprendere meglio come vengono implementati i blocchi, è utile leggere informazioni su Phaser in questo articolo: Guide to the Java Phaser . E parlando di vari sincronizzatori, devi leggere l' articolo di DZone su The Java Synchronizers.

Conclusione

In questa recensione, abbiamo esaminato i principali modi in cui i thread interagiscono in Java. Materiale aggiuntivo: Meglio insieme: Java e la classe Thread. Parte I — Thread di esecuzione Meglio insieme: Java e la classe Thread. Parte III — Interazione Meglio insieme: Java e la classe Thread. Parte IV — Callable, Future e friends Better together: Java e la classe Thread. Parte V — Executor, ThreadPool, Fork/Join Better insieme: Java e la classe Thread. Parte VI - Spara via!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION