CodeGym /Blog Java /Random-FR /Mieux ensemble : Java et la classe Thread. Partie VI — Ti...
John Squirrels
Niveau 41
San Francisco

Mieux ensemble : Java et la classe Thread. Partie VI — Tirez !

Publié dans le groupe Random-FR

Introduction

Les fils 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, à partir de « Mieux ensemble : Java et la classe Thread. Partie I — Threads d'exécution », nous savons que la classe Thread représente un thread d'exécution. Nous savons qu'un thread effectue une tâche. Si nous voulons que nos tâches puissent run, nous devons marquer le fil avec Runnable. Mieux ensemble : Java et la classe Thread.  Partie VI — Tirez !  - 1Pour rappel, nous pouvons utiliser le compilateur Java en ligne de Tutorialspoint :

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 aussi que nous avons quelque chose qui s'appelle un verrou. Nous avons appris cela dans " Better together: Java and the Thread class. Part II — Synchronization . Si un thread acquiert un verrou, alors un autre thread essayant d'acquérir le verrou sera obligé d'attendre que le verrou soit libéré :

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 des autres choses intéressantes que nous pouvons faire.

Sémaphores

Le moyen le plus simple de contrôler le nombre de threads pouvant s'exécuter simultanément est un sémaphore. C'est comme un signal ferroviaire. Vert signifie continuer. Rouge signifie attendre. Attendre quoi du sémaphore ? Accès. Pour y avoir accès, il faut l'acquérir. Et lorsque l'accès n'est plus nécessaire, nous devons le donner ou le libérer. Voyons comment cela fonctionne. Nous devons importer la java.util.concurrent.Semaphoreclasse. 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 vous pouvez le voir, ces opérations (acquire et release) nous aident à comprendre le fonctionnement d'un sémaphore. La chose la plus importante est que si nous voulons avoir accès, le sémaphore doit avoir un nombre positif de permis. Ce décompte peut être initialisé à un nombre négatif. Et nous pouvons demander (acquérir) plus d'un permis.

CountDownLatch

Le mécanisme suivant est CountDownLatch. Sans surprise, il s'agit d'un verrou avec un compte à rebours. Ici, nous avons besoin de l'instruction d'importation appropriée pour la java.util.concurrent.CountDownLatchclasse. C'est comme une course à pied, où tout le monde se rassemble sur la ligne de départ. Et une fois que tout le monde est prêt, tout le monde reçoit le signal de départ en même temps et démarre simultanément. 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();
 	}
}
Tout d'abord, nous disons d'abord au verrou de countDown(). Google définit le compte à rebours comme "un acte de comptage des chiffres dans l'ordre inverse jusqu'à zéro". Et puis nous disons au verrou de await(), c'est-à-dire d'attendre que le compteur devienne zéro. Fait intéressant, il s'agit d'un compteur unique. La documentation Java indique: "Lorsque les threads doivent décompter à plusieurs reprises de cette manière, utilisez plutôt un CyclicBarrier". En d'autres termes, si vous avez besoin d'un compteur réutilisable, vous avez besoin d'une option différente : CyclicBarrier.

Barrière Cyclique

Comme son nom l'indique, CyclicBarrierest une barrière "réutilisable". Nous devrons importer la java.util.concurrent.CyclicBarrierclasse. Regardons un exemple :

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();
	}
}
Comme vous pouvez le voir, le thread exécute la awaitméthode, c'est-à-dire qu'il attend. Dans ce cas, la valeur barrière diminue. La barrière est considérée comme brisée ( barrier.isBroken()) lorsque le compte à rebours atteint zéro. Pour réinitialiser la barrière, vous devez appeler la reset()méthode, qui CountDownLatchn'en a pas.

Echangeur

Le mécanisme suivant est Exchanger. Dans ce contexte, un échange est un point de synchronisation où les choses changent être échangées ou permutées. Comme vous vous en doutez, an Exchangerest une classe qui effectue un échange ou une permutation. Prenons l'exemple le plus 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() + " exchanged with " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
Ici, nous commençons deux fils. Chacun d'eux exécute la méthode d'échange et attend que l'autre thread exécute également la méthode d'échange. Ce faisant, les threads échangent les arguments passés. Intéressant. Ça ne vous rappelle pas quelque chose ? Cela rappelle SynchronousQueue, qui est au cœur de CachedThreadPool. 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 que lorsqu'un nouveau thread est démarré, il attendra, car la file d'attente sera vide. Et puis le thread principal place la chaîne "Message" dans la file d'attente. De plus, il s'arrêtera également jusqu'à ce que cette chaîne soit reçue de la file d'attente. Vous pouvez également lire " SynchronousQueue vs Exchanger " pour en savoir plus sur ce sujet.

Phaseur

Nous avons gardé le meilleur pour la fin — Phaser. Nous devrons importer la java.util.concurrent.Phaserclasse. Prenons un exemple simple :

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'exemple illustre que lors de l'utilisation de Phaser, la barrière se brise lorsque le nombre d'inscriptions correspond au nombre d'arrivées à la barrière. Vous pouvez vous familiariser avec Phaseren lisant cet article GeeksforGeeks .

Résumé

Comme vous pouvez le voir dans ces exemples, il existe différentes manières de synchroniser les threads. Plus tôt, j'ai essayé de me souvenir des aspects du multithreading. J'espère que les épisodes précédents de cette série ont été utiles. Certaines personnes disent que le chemin vers le multithreading commence avec le livre "Java Concurrency in Practice". Bien qu'il soit sorti en 2006, les gens disent que le livre est assez fondamental et toujours d'actualité aujourd'hui. Par exemple, vous pouvez lire la discussion ici : Is "Java Concurrency In Practice" still valid? . Il est également utile de lire les liens dans la discussion. Par exemple, il y a un lien vers le livre The Well-Grounded Java Developer , et nous ferons une mention particulière du Chapter 4. Modern concurrency . Il y a aussi une revue complète sur ce sujet:La "concurrence Java dans la pratique" est-elle toujours valable à l'ère de Java 8 ? Cet article propose également des suggestions sur ce qu'il faut lire pour vraiment comprendre ce sujet. Après cela, vous pouvez jeter un œil à un excellent livre comme OCA/OCP Java SE 8 Programmer Practice Tests . Nous nous intéressons au second acronyme : OCP (Oracle Certified Professional). Vous trouverez des tests dans le "Chapitre 20 : Concurrence Java". Ce livre contient à la fois des questions et des réponses avec des explications. Par exemple : Mieux ensemble : Java et la classe Thread.  Partie VI — Tirez !  - 3Beaucoup de gens pourraient commencer à dire que cette question est encore un autre exemple de mémorisation de méthodes. D'un côté, oui. En revanche, vous pourriez répondre à cette question en rappelant qu'il ExecutorServices'agit d'une sorte de "mise à jour" de Executor. EtExecutorest destiné à masquer simplement la façon dont les threads sont créés, mais ce n'est pas le moyen principal de les exécuter, c'est-à-dire de démarrer un Runnableobjet sur un nouveau thread. C'est pourquoi il n'y a pas execute(Callable)— parce que dans ExecutorService, le Executorajoute simplement submit()des méthodes qui peuvent renvoyer un Futureobjet. Bien sûr, nous pouvons mémoriser une liste de méthodes, mais il est beaucoup plus facile de faire notre réponse en fonction de notre connaissance de la nature des classes elles-mêmes. Et voici quelques documents supplémentaires sur le sujet : Mieux ensemble : Java et la classe Thread. Partie I — Threads d'exécution Mieux ensemble : Java et la classe Thread. Partie II — Synchronisation Mieux ensemble : Java et la classe Thread. Partie III — Interaction Mieux ensemble : Java et la classe Thread. Partie IV — Callable, Future et friends Mieux ensemble : Java et la classe Thread. Partie V — Executor, ThreadPool, Fork/Join
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION