JavaRush /Java Blog /Random-IT /Non puoi rovinare Java con una discussione: Parte VI - Al...
Viacheslav
Livello 3

Non puoi rovinare Java con una discussione: Parte VI - Alla barriera!

Pubblicato nel gruppo Random-IT

introduzione

Gli stream sono una cosa interessante. Nelle revisioni precedenti abbiamo esaminato alcuni degli strumenti disponibili per implementare il multithreading. Vediamo quali altre cose interessanti possiamo fare. A questo punto sappiamo molto. Ad esempio, da " Non puoi rovinare Java con un thread: Parte I - Discussioni ", sappiamo che un thread è un thread. Sappiamo che un thread sta eseguendo alcune attività. Se vogliamo che la nostra attività possa essere eseguita ( run), allora dobbiamo specificare che il thread sia un certo Runnable. Non puoi rovinare Java con una discussione: Parte VI - Alla barriera!  -1Per ricordarlo, possiamo usare Tutorialspoint Java Online 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 un concetto come lucchetto. Ne abbiamo letto in " Non puoi rovinare Java con un thread: Parte II - Sincronizzazione ". Un thread può occupare un lock e quindi un altro thread che tenta di occupare il lock sarà costretto ad attendere che il lock si liberi:
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 cos’altro possiamo fare di interessante.

Semafori

Il mezzo più semplice per controllare quanti thread possono funzionare contemporaneamente è un semaforo. Come sulla ferrovia. La luce verde è accesa: puoi. La luce rossa è accesa: stiamo aspettando. Cosa ci aspettiamo da un semaforo? Autorizzazioni. Autorizzazione in inglese - permesso. Per ottenere il permesso è necessario ottenerlo, che in inglese si dirà acquire. E quando il permesso non serve più, bisogna darlo via, cioè rilasciarlo o liberarsene, che in inglese si dice rilascio. Vediamo come funziona. Dovremo importare la classe java.util.concurrent.Semaphore. 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 possiamo vedere, avendo memorizzato le parole inglesi, capiamo come funziona il semaforo. È interessante notare che la condizione principale è che il "conto" del semaforo abbia un numero positivo di permessi. Pertanto, puoi avviarlo con un segno meno. E puoi richiederne (acquisirne) più di 1.

Conteggio alla rovescia

Il meccanismo successivo è CountDownLatch. CountDown in inglese è un conto alla rovescia e Latch è un bullone o un chiavistello. Cioè, se lo traduciamo, allora questo è un fermo con un conto alla rovescia. Qui abbiamo bisogno dell'importazione appropriata della classe java.util.concurrent.CountDownLatch. È come una corsa o una corsa in cui tutti si riuniscono sulla linea di partenza e quando tutti sono pronti viene dato il permesso e tutti partono 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();
 	}
}
wait in inglese - aspettarsi. Cioè, parliamo prima countDown. Come afferma Google Translator, il conto alla rovescia è "l'atto di contare i numeri in ordine inverso fino allo zero", ovvero eseguire un'azione di conto alla rovescia, il cui scopo è contare fino a zero. E poi diciamo await, cioè aspettiamo che il valore del contatore diventi zero. È interessante notare che un contatore del genere è usa e getta. Come è detto nel JavaDoc - "Quando i thread devono eseguire ripetutamente il conto alla rovescia in questo modo, utilizzare invece CyclicBarrier", ovvero se è necessario un conteggio riutilizzabile, è necessario utilizzare un'altra opzione, denominata CyclicBarrier.

Barriera ciclica

Come suggerisce il nome, CyclicBarriersi tratta di una barriera ciclica. Dovremo importare la classe java.util.concurrent.CyclicBarrier. Diamo un'occhiata ad un esempio:
public static void main(String[] args) throws InterruptedException {
	Runnable action = () -> System.out.println("На старт!");
	CyclicBarrier berrier = new CyclicBarrier(3, action);
	Runnable task = () -> {
		try {
			berrier.await();
			System.out.println("Finished");
		} catch (BrokenBarrierException | InterruptedException e) {
			e.printStackTrace();
		}
	};
	System.out.println("Limit: " + berrier.getParties());
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
	}
}
Come puoi vedere, il thread è in esecuzione await, ovvero in attesa. In questo caso il valore della barriera diminuisce. La barriera è considerata rotta ( berrier.isBroken()) quando il conto alla rovescia raggiunge lo zero. Per ripristinare la barriera è necessario chiamare berrier.reset(), cosa che mancava in CountDownLatch.

Scambiatore

Il prossimo rimedio è Exchanger. Lo scambio dall'inglese è tradotto come scambio o scambio. A Exchangerè uno scambiatore, cioè qualcosa attraverso il quale si scambiano. Diamo un'occhiata ad un semplice esempio:
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() + " обменялся с " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
Qui lanciamo due thread. Ognuno di essi esegue il metodo di scambio e attende che anche un altro thread esegua lo stesso metodo. Pertanto, i thread si scambieranno tra loro gli argomenti passati. Cosa interessante. Non ti ricorda niente? E ricorda SynchronousQueue, che sta al centro dell'a 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 avviando un nuovo thread, questo thread andrà in modalità di attesa, perché la coda sarà vuota. E poi mainil thread metterà in coda il testo "Messaggio". Allo stesso tempo si fermerà per il tempo richiesto finché non riceverà questo elemento di testo dalla coda. Su questo argomento puoi leggere anche " SynchronousQueue Vs Exchanger ".

Faser

E infine, la cosa più dolce - Phaser. Dovremo importare la classe java.util.concurrent.Phaser. Diamo un'occhiata ad un semplice esempio:
public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser();
        // Вызывая метод register, мы регистрируем текущий поток (main) How участника
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // Через 3 секунды прибываем к барьеру и снимаемся регистрацию. Кол-во прибывших = кол-во регистраций = пуск
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // Говорим, что будет +1 участник на Phaser
        phaser.register();
        // Запускаем новый поток
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); //threads register arrival to the phaser.
            System.out.println(name + " after passing barrier");
        }).start();
    }
L'esempio mostra che la barriera, quando si utilizza Phaser'a, viene superata quando il numero di registrazioni coincide con il numero di arrivi alla barriera. Potete saperne di più Phasernell'articolo dell'hub " Nuovo sincronizzatore Phaser ".

Risultati

Come puoi vedere dagli esempi, ci sono diversi modi per sincronizzare i thread. Prima ho provato a ricordare qualcosa sul multithreading, spero che le parti precedenti siano state utili. Dicono che il percorso verso il multithreading inizia con il libro "Java Concurrency in Practice". Sebbene sia uscito nel 2006, le persone rispondono che il libro è piuttosto fondamentale e ha ancora un impatto. Ad esempio, puoi leggere le discussioni qui: " Java Concurrency In Practice è ancora valido? ". È anche utile leggere i collegamenti dalla discussione. Ad esempio, c'è un collegamento al libro " The Well-Grounded Java Developer ", in cui vale la pena prestare attenzione al " Capitolo 4. Concorrenza moderna ". C'è un'altra recensione completa sullo stesso argomento: " La concorrenza Java nella pratica è ancora rilevante nell'era di Java 8 ". Contiene anche suggerimenti su cos'altro dovresti leggere per comprendere veramente l'argomento. Successivamente, puoi dare un'occhiata più da vicino a un libro meraviglioso come " OCA OCP JavaSE 8 Programmer Practice Tests ". A noi interessa la seconda parte, ovvero OCP. E ci sono test in "∫". Questo libro contiene sia domande che risposte con spiegazioni. Ad esempio: Non puoi rovinare Java con una discussione: Parte VI - Alla barriera!  - 3molti potrebbero iniziare a dire che questa è solo un'altra memorizzazione di metodi. Da un lato sì. D'altronde a questa domanda si può rispondere ricordando che ExecutorServicesi tratta di una sorta di “upgrade” Executor. E Executorha lo scopo semplicemente di nascondere il metodo di creazione dei thread, ma non il modo principale per eseguirli, ovvero l'esecuzione in un nuovo thread Runnable. Quindi execute(Callable)no, perché hanno semplicemente aggiunto metodi ExecutorServiceche possono restituire . Come puoi vedere, possiamo memorizzare un elenco di metodi, ma è molto più semplice indovinarli se conosciamo la natura delle classi stesse. Bene, alcuni materiali aggiuntivi sull'argomento: ExecutorsubmitFuture #Viacheslav
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION