JavaRush /Blog Java /Random-FR /Vous ne pouvez pas gâcher Java avec un fil de discussion ...
Viacheslav
Niveau 3

Vous ne pouvez pas gâcher Java avec un fil de discussion : Partie VI – À la barrière !

Publié dans le groupe Random-FR

Introduction

Les streams sont une chose intéressante. Dans les revues précédentes, nous avons examiné certains des outils disponibles pour implémenter le multithreading. Voyons quelles autres choses intéressantes nous pouvons faire. À ce stade, nous en savons beaucoup. Par exemple, dans « Vous ne pouvez pas gâcher Java avec un fil : partie I - Threads », nous savons qu'un fil est un fil. Nous savons qu'un thread effectue une tâche. Si nous voulons que notre tâche puisse s'exécuter ( run), alors nous devons spécifier que le thread est un certain Runnable. Vous ne pouvez pas gâcher Java avec un fil de discussion : Partie VI – À la barrière !  - 1Pour rappel, on peut utiliser 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();
}
Nous savons également que nous avons un concept tel que le verrouillage. Nous en avons entendu parler dans « Vous ne pouvez pas gâcher Java avec un fil : partie II - Synchronisation ». Un thread peut occuper un verrou puis un autre thread qui tente d'occuper le verrou sera obligé d'attendre que le verrou se libère :
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();
	}
}
Je pense qu’il est temps de parler de ce que nous pouvons faire d’autre d’intéressant.

Sémaphores

Le moyen le plus simple de contrôler le nombre de threads pouvant fonctionner simultanément est un sémaphore. Comme sur le chemin de fer. Le feu vert est allumé, vous pouvez. Le feu rouge est allumé, nous attendons. Qu'attend-on d'un sémaphore ? Autorisations. Permission en anglais - permis. Pour obtenir l'autorisation, vous devez l'obtenir, qui sera acquise en anglais. Et lorsque l’autorisation n’est plus nécessaire, nous devons la donner, c’est-à-dire la libérer ou nous en débarrasser, ce qui en anglais sera release. Voyons voir comment ça fonctionne. Nous devrons importer la classe java.util.concurrent.Semaphore. Exemple:
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);
}
Comme nous pouvons le constater, après avoir mémorisé des mots anglais, nous comprenons comment fonctionne le sémaphore. Il est intéressant de noter que la condition principale est que le « compte » sémaphore doit avoir un nombre positif de permis. Par conséquent, vous pouvez le lancer avec un moins. Et vous pouvez en demander (acquérir) plus de 1.

Compte à rebours

Le mécanisme suivant est CountDownLatch. CountDown en anglais est un compte à rebours et Latch est un boulon ou un loquet. Autrement dit, si nous le traduisons, il s'agit d'un loquet avec un compte à rebours. Ici, nous avons besoin d'une importation appropriée de la classe java.util.concurrent.CountDownLatch. C'est comme une course ou une course où tout le monde se rassemble sur la ligne de départ et quand tout le monde est prêt, la permission est donnée et tout le monde démarre en même temps. Exemple:
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();
 	}
}
attendre en anglais - s'attendre. Autrement dit, nous parlons en premier countDown. Comme le dit Google Translator, le compte à rebours est « un acte consistant à compter des chiffres dans l'ordre inverse jusqu'à zéro », c'est-à-dire effectuer une action de compte à rebours dont le but est de compter jusqu'à zéro. Et puis nous disons await- c'est-à-dire attendez que la valeur du compteur devienne nulle. Il est intéressant de noter qu'un tel compteur est jetable. Comme il est dit dans le JavaDoc - "Lorsque les threads doivent décompter à plusieurs reprises de cette manière, utilisez plutôt un CyclicBarrier", c'est-à-dire que si vous avez besoin d'un décompte réutilisable, vous devez utiliser une autre option, appelée CyclicBarrier.

Barrière cyclique

Comme son nom l’indique, CyclicBarrieril s’agit d’une barrière cyclique. Nous devrons importer la classe java.util.concurrent.CyclicBarrier. Regardons un exemple :
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();
	}
}
Comme vous pouvez le voir, le thread est en cours d'exécution await, c'est-à-dire en attente. Dans ce cas, la valeur de la barrière diminue. La barrière est considérée comme brisée ( berrier.isBroken()) lorsque le compte à rebours atteint zéro. Pour réinitialiser la barrière, vous devez appeler berrier.reset(), ce qui manquait dans CountDownLatch.

Échangeur

Le prochain remède est Exchanger. L'échange de l'anglais est traduit par échange ou échange. A Exchangerest un échangeur, c'est-à-dire quelque chose par lequel ils échangent. Regardons un exemple simple :
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();
}
Ici, nous lançons deux fils de discussion. Chacun d'eux exécute la méthode d'échange et attend qu'un autre thread exécute également la méthode d'échange. Ainsi, les threads échangeront entre eux les arguments transmis. Chose intéressante. Elle ne vous rappelle rien ? Et il rappelle SynchronousQueue, ce qui est au cœur de cachedThreadPool'a. Pour plus de clarté, voici un exemple :
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'exemple montre qu'en lançant un nouveau thread, ce thread passera en mode attente, car la file d'attente sera vide. Et puis mainle fil mettra en file d’attente le texte « Message ». Dans le même temps, il s'arrêtera pendant le temps requis jusqu'à ce qu'il reçoive cet élément de texte de la file d'attente. Sur ce sujet vous pouvez également lire « SynchronousQueue Vs Exchanger ».

Phaseur

Et enfin, la chose la plus douce - Phaser. Nous devrons importer la classe java.util.concurrent.Phaser. Regardons un exemple simple :
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'exemple montre que la barrière, lorsqu'on utilise Phaser'a, est franchie lorsque le nombre d'inscriptions coïncide avec le nombre d'arrivées à la barrière. Vous pouvez en savoir plus Phaserdans l'article du hub « Nouveau synchroniseur Phaser ».

Résultats

Comme vous pouvez le voir dans les exemples, il existe différentes manières de synchroniser les threads. Plus tôt, j'ai essayé de me souvenir de quelque chose sur le multithreading, j'espère que les parties précédentes ont été utiles. On dit que le chemin vers le multithreading commence avec le livre "Java Concurrency in Practice". Bien qu’il soit sorti en 2006, les gens répondent que le livre est assez fondamental et qu’il a toujours du punch. Par exemple, vous pouvez lire les discussions ici : « Java Concurrency In Practice est-il toujours valide ? ». Il est également utile de lire les liens de la discussion. Par exemple, il existe un lien vers le livre " The Well-Grounded Java Developer ", dans lequel il convient de prêter attention au " Chapitre 4. Concurrence moderne ". Il existe une autre revue complète sur le même sujet : « La co-monnaie Java est-elle en pratique toujours pertinente à l'ère de Java 8 ». Il contient également des conseils sur ce que vous devriez lire d'autre pour vraiment comprendre le sujet. Après cela, vous pourrez examiner de plus près un livre aussi merveilleux que " OCA OCP JavaSE 8 Programmer Practice Tests ". Nous nous intéressons à la deuxième partie, à savoir OCP. Et il y a des tests en "∫". Ce livre contient à la fois des questions et des réponses avec des explications. Par exemple : Vous ne pouvez pas gâcher Java avec un fil de discussion : Partie VI – À la barrière !  - 3Beaucoup pourraient commencer à dire qu’il ne s’agit là que d’une autre mémorisation de méthodes. D'une part, oui. D'un autre côté, on peut répondre à cette question en rappelant qu'il ExecutorServices'agit d'une sorte de « mise à niveau » Executor. Et Executoril vise simplement à masquer la méthode de création des threads, mais pas le principal moyen de les exécuter, c'est-à-dire de les exécuter dans un nouveau thread Runnable. Donc execute(Callable)non, parce que ils ont simplement ajouté des méthodes ExecutorServicequi peuvent renvoyer des fichiers . Comme vous pouvez le constater, nous pouvons mémoriser une liste de méthodes, mais il est beaucoup plus facile de la deviner si nous connaissons la nature des classes elles-mêmes. Eh bien, quelques documents supplémentaires sur le sujet : ExecutorsubmitFuture #Viacheslav
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION