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

Meglio insieme: Java e la classe Thread. Parte VI - Spara via!

Pubblicato nel gruppo Random-IT

introduzione

I thread sono una cosa interessante. Nelle revisioni precedenti, abbiamo esaminato alcuni degli strumenti disponibili per l'implementazione del multithreading. Vediamo quali altre cose interessanti possiamo fare. A questo punto, sappiamo molto. Ad esempio, da " Better together: Java and the Thread class. Part I — Threads of execution ", sappiamo che la classe Thread rappresenta un thread di esecuzione. Sappiamo che un thread esegue un compito. Se vogliamo che i nostri compiti siano in grado di farlo run, dobbiamo contrassegnare il thread con Runnable. Meglio insieme: Java e la classe Thread.  Parte VI - Spara via!  - 1Per ricordare, possiamo usare il Tutorialspoint Online Java Compiler :

public static void main(String[] args){
	Runnable task = () -> {
 		Thread thread = Thread.currentThread();
		System.out.println("Hello from " + thread.getName());
	};
	Thread thread = new Thread(task);
	thread.start();
}
Sappiamo anche che abbiamo qualcosa chiamato lucchetto. Ne abbiamo appreso in " Better together: Java and the Thread class. Part II — Synchronization . Se un thread acquisisce un blocco, un altro thread che tenta di acquisire il blocco sarà costretto ad attendere il rilascio del blocco:

import java.util.concurrent.locks.*;

public class HelloWorld{
	public static void main(String []args){
		Lock lock = new ReentrantLock();
		Runnable task = () -> {
			lock.lock();
			Thread thread = Thread.currentThread();
			System.out.println("Hello from " + thread.getName());
			lock.unlock();
		};
		Thread thread = new Thread(task);
		thread.start();
	}
}
Penso che sia il momento di parlare di quali altre cose interessanti possiamo fare.

Semafori

Il modo più semplice per controllare quanti thread possono essere eseguiti contemporaneamente è un semaforo. È come un segnale ferroviario. Verde significa procedere. Il rosso significa aspettare. Aspetta cosa dal semaforo? Accesso. Per ottenere l'accesso, dobbiamo acquisirlo. E quando l'accesso non è più necessario, dobbiamo darlo via o rilasciarlo. Vediamo come funziona. Dobbiamo importare la java.util.concurrent.Semaphoreclasse. Esempio:

public static void main(String[] args) throws InterruptedException {
	Semaphore semaphore = new Semaphore(0);
	Runnable task = () -> {
		try {
			semaphore.acquire();
			System.out.println("Finished");
			semaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	Thread.sleep(5000);
	semaphore.release(1);
}
Come puoi vedere, queste operazioni (acquisizione e rilascio) ci aiutano a capire come funziona un semaforo. La cosa più importante è che se vogliamo accedere, allora il semaforo deve avere un numero positivo di permessi. Questo conteggio può essere inizializzato su un numero negativo. E possiamo richiedere (acquisire) più di 1 permesso.

Conto alla rovesciaLatch

Il meccanismo successivo è CountDownLatch. Non sorprende che questo sia un fermo con un conto alla rovescia. Qui abbiamo bisogno dell'istruzione di importazione appropriata per la java.util.concurrent.CountDownLatchclasse. È come una corsa podistica, dove tutti si radunano sulla linea di partenza. E una volta che tutti sono pronti, tutti ricevono il segnale di partenza contemporaneamente e iniziano contemporaneamente. Esempio:

public static void main(String[] args) {
	CountDownLatch countDownLatch = new CountDownLatch(3);
	Runnable task = () -> {
		try {
			countDownLatch.countDown();
			System.out.println("Countdown: " + countDownLatch.getCount());
			countDownLatch.await();
			System.out.println("Finished");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
 	}
}
Innanzitutto, per prima cosa diciamo al fermo di countDown(). Google definisce il conto alla rovescia come "un atto di contare i numeri in ordine inverso a zero". E poi diciamo al fermo di await(), cioè aspettiamo che il contatore diventi zero. È interessante notare che questo è un contatore una tantum. La documentazione Java dice: "Quando i thread devono contare ripetutamente in questo modo, usa invece un CyclicBarrier". In altre parole, se hai bisogno di un contatore riutilizzabile, hai bisogno di un'opzione diversa: CyclicBarrier.

Barriera ciclica

Come suggerisce il nome, CyclicBarrierè una barriera "riutilizzabile". Avremo bisogno di importare la java.util.concurrent.CyclicBarrierclasse. Diamo un'occhiata a un esempio:

public static void main(String[] args) throws InterruptedException {
	Runnable action = () -> System.out.println("On your mark!");
	CyclicBarrier barrier = new CyclicBarrier(3, action);
	Runnable task = () -> {
		try {
			barrier.await();
			System.out.println("Finished");
		} catch (BrokenBarrierException | InterruptedException e) {
			e.printStackTrace();
		}
	};
	System.out.println("Limit: " + barrier.getParties());
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
	}
}
Come puoi vedere, il thread esegue il awaitmetodo, cioè attende. In questo caso, il valore della barriera diminuisce. La barriera è considerata interrotta ( barrier.isBroken()) quando il conto alla rovescia raggiunge lo zero. Per reimpostare la barriera, è necessario chiamare il reset()metodo, che CountDownLatchnon ha.

Scambiatore

Il meccanismo successivo è Exchanger. In questo contesto, uno scambio è un punto di sincronizzazione in cui le cose cambiano essere scambiate o scambiate. Come ci si aspetterebbe, an Exchangerè una classe che esegue uno scambio o uno scambio. Diamo un'occhiata all'esempio più semplice:

public static void main(String[] args) {
	Exchanger<String> exchanger = new Exchanger<>();
	Runnable task = () -> {
		try {
			Thread thread = Thread.currentThread();
			String withThreadName = exchanger.exchange(thread.getName());
			System.out.println(thread.getName() + " exchanged with " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
Qui iniziamo due thread. Ciascuno di essi esegue il metodo di scambio e attende che anche l'altro thread esegua il metodo di scambio. In tal modo, i thread si scambiano gli argomenti passati. Interessante. Non ti ricorda qualcosa? Ricorda SynchronousQueue, che è al centro di CachedThreadPool. Per chiarezza, ecco un esempio:

public static void main(String[] args) throws InterruptedException {
	SynchronousQueue<String> queue = new SynchronousQueue<>();
	Runnable task = () -> {
		try {
			System.out.println(queue.take());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	queue.put("Message");
}
L'esempio mostra che quando viene avviato un nuovo thread, attenderà, perché la coda sarà vuota. E poi il thread principale mette in coda la stringa "Message". Inoltre, si fermerà anche fino a quando questa stringa non verrà ricevuta dalla coda. Puoi anche leggere " SynchronousQueue vs Exchanger " per saperne di più su questo argomento.

Phaser

Abbiamo tenuto il meglio per ultimo — Phaser. Avremo bisogno di importare la java.util.concurrent.Phaserclasse. Diamo un'occhiata a un semplice esempio:

public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser();
        // By calling the register method, we register the current (main) thread as a party
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // We indicate that there will be a +1 party on the Phaser
        phaser.register();
        // Start a new thread
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
            System.out.println(name + " after passing barrier");
        }).start();
    }
L'esempio illustra che quando si utilizza Phaser, la barriera si rompe quando il numero di registrazioni corrisponde al numero di arrivi alla barriera. Puoi acquisire maggiore familiarità Phaserleggendo questo articolo di GeeksforGeeks .

Riepilogo

Come puoi vedere da questi esempi, ci sono vari modi per sincronizzare i thread. In precedenza, ho cercato di ricordare gli aspetti del multithreading. Spero che le puntate precedenti di questa serie siano state utili. Alcune persone dicono che il percorso verso il multithreading inizia con il libro "Java Concurrency in Practice". Sebbene sia stato pubblicato nel 2006, la gente dice che il libro è abbastanza fondamentale e ancora attuale oggi. Ad esempio, puoi leggere la discussione qui: "Java Concurrency In Practice" è ancora valido? . È anche utile leggere i collegamenti nella discussione. Per esempio, c'è un link al libro The Well-Grounded Java Developer , e faremo particolare menzione al Capitolo 4. Modern concurrency . C'è anche un'intera recensione su questo argomento:"Java Concurrency in Practice" è ancora valido nell'era di Java 8? Quell'articolo offre anche suggerimenti su cos'altro leggere per comprendere veramente questo argomento. Successivamente, potresti dare un'occhiata a un ottimo libro come OCA/OCP Java SE 8 Programmer Practice Tests . A noi interessa il secondo acronimo: OCP (Oracle Certified Professional). Troverai i test nel "Capitolo 20: Java Concurrency". Questo libro contiene sia domande che risposte con spiegazioni. Ad esempio: Meglio insieme: Java e la classe Thread.  Parte VI - Spara via!  - 3molte persone potrebbero iniziare a dire che questa domanda è un altro esempio di memorizzazione dei metodi. Da un lato sì. D'altra parte, potresti rispondere a questa domanda ricordando che ExecutorServiceè una sorta di "aggiornamento" di Executor. EExecutorha lo scopo di nascondere semplicemente il modo in cui vengono creati i thread, ma non è il modo principale per eseguirli, ovvero avviare un Runnableoggetto su un nuovo thread. Ecco perché non c'è execute(Callable), perché in ExecutorService, Executorsemplicemente aggiunge submit()metodi che possono restituire un Futureoggetto. Naturalmente, possiamo memorizzare un elenco di metodi, ma è molto più semplice formulare la nostra risposta in base alla nostra conoscenza della natura delle classi stesse. E qui ci sono alcuni materiali aggiuntivi sull'argomento: Meglio insieme: Java e la classe Thread. Parte I — Thread di esecuzione Meglio insieme: Java e la classe Thread. Parte II — Sincronizzazione 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 — Esecutore, ThreadPool, Fork/Join
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION