Java >> Tutoriel Java >  >> Java

Multithreading en Java Tutoriel

Dans cet article, nous présentons un didacticiel complet sur le multithreading en Java. Le multithreading est la capacité d'un programme à gérer son utilisation par plusieurs utilisateurs et même à gérer plusieurs requêtes par le même utilisateur. Dans le langage de programmation Java, la programmation concurrente est l'exécution de processus et de threads. Java a pris en charge java.lang.Thread depuis JDK 1.0. Le java.util.concurrent L'API est publiée dans Java 5. Collections Java – Vector , Stack et HashTable sont thread-safe. Java fournit une fonctionnalité synchronisée pour prendre en charge la sécurité des threads sur des collections telles que Collections.SynchronizedCollection() et Collections.SynchronizedMap() .

Vous pouvez également consulter ce tutoriel dans la vidéo suivante :

Table des matières

1. Processus et fil
2. Cycle de vie d'un fil
3. Technologies utilisées
4. Projet Maven
4.1 Dépendances
4.2 CommonUtil
5. Créer un fil
5.1 Étend la classe Thread
5.2 Implémente Runnable
5.3 ThreadTestBase
5.4 ThreadExampleTest
5.5 RunnableThreadExampleTest
6. Détails du sujet
6.1 Priorité des threads
6.2 Usine de threads
6.3 Service d'exécuteur
6.4 Notification et attente de thread
7. Multithreading en Java
8. Synchronisation
8.1 Conditions de concurrence
8.2 Bloc synchronisé
8.3 Méthode synchronisée
8.4 Verrouiller
8.5 Entier atomique
9. Résumé
10. Télécharger le code source

1. Processus et fil

Un processus est l'exécution d'un programme et un thread est une seule exécution de travail au sein du processus. Un processus peut contenir plusieurs threads. Un thread est également appelé processus léger.

En Java, un processus est exécuté indépendamment des autres processus dans une JVM et les threads d'une JVM partagent le tas appartenant à ce processus. C'est pourquoi plusieurs threads peuvent accéder au même objet dans le tas. Les threads partagent le tas et ont leur propre espace de pile. Par conséquent, l'invocation d'une méthode et de ses variables locales sont protégées contre les threads des autres threads, tandis que le tas n'est pas thread-safe et doit être synchronisé pour les programmes multithreads.

2. Cycle de vie d'un fil

Un thread peut se trouver dans différents états de son cycle de vie. Le schéma ci-dessous affiche les différents états d'un thread qui sont start, run, sleep/wait/block et done.

  • Nouveau :Lorsqu'un fil est créé, il est dans le nouvel état.
  • Exécutable : Un thread attend son tour pour être sélectionné pour exécution. Le thread est sélectionné par le planificateur de threads en fonction des priorités des threads. Le start() enregistre un thread dans un planificateur de threads.
  • En cours d'exécution : Le processeur exécute le thread. Le thread s'exécute jusqu'à ce qu'il soit bloqué ou abandonne son tour avec Thread.yield() . En raison de la surcharge du changement de contexte, yield() ne doit pas être utilisé très fréquemment. Le wait() , sleep() , et join() les méthodes font que le thread quitte l'état en cours d'exécution.
  • En attente : Un thread attend qu'un autre thread exécute une tâche.
  • Dormir : Les threads Java sont forcés de dormir (suspendus) avec cette méthode surchargée :Thread.sleep(milliseconds) , Thread.sleep(milliseconds, nanoseconds) .
  • Bloqué sur les E/S : Un thread est bloqué sur certains traitements d'E/S externes pour terminer. Le fil passera à Runnable après la condition d'E/S comme la lecture d'octets de données, etc.
  • Bloqué lors de la synchronisation : Le fil passera à Runnable lorsqu'un verrou est acquis.
  • Résilié : Le fil a terminé son travail.

3. Technologies utilisées

L'exemple de code de cet article a été construit et exécuté en utilisant :

  • Java 11
  • Maven 3.3.9
  • Éclipse Oxygène
  • Logback 1.2.3
  • Junit 4.12

4. Projet Maven

Dans cette étape, je vais créer un projet Maven pour démontrer le multi-threading en Java.

4.1 Dépendances

Ajouter Logback et Junit bibliothèques au pom.xml .

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>java-multithreads-demo</groupId>
	<artifactId>java-multithreads-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.0</version>
				<configuration>
					<release>11</release>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-access</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
			<version>1.2.3</version>
		</dependency>
	</dependencies>
</project>

4.2 CommonUtil

Dans cette étape, je vais créer un CommonUtil classe qui contient les constantes – THREAD_STARTED , THREAD_FINISHED – et THREAD_STATE et deux méthodes - fiveSecondsProcess() et waitforThread() .

CommonUtil.java

package jcg.zheng.multithread.demo;

import java.util.concurrent.TimeUnit;

public class CommonUtil {

	public static final String THREAD_FINISHED = " Thread finished";
	public static final String THREAD_STARTED = " Thread started";
	public static final String THREAD_STATE = "Thread state = ";

	public static void fiveSecondsProcess() {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void waitForThread(Thread thread) {
		try {
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

5. Créer un fil

Java Thread a neuf constructeurs pour créer un nouveau thread. Nous classons en deux catégories :

  • Étend Thread classe
  • Mise en œuvre Runnable interface

5.1 Étend la classe Thread

Le Thread la classe elle-même implémente le Runnable interface. Lorsqu'une classe étend Thread , il doit remplacer le run() méthode et fournir sa propre implémentation de run() .

Le start() méthode dans le Thread La classe démarre l'exécution d'un thread. Le thread sera actif jusqu'à ce que l'exécution de la méthode run soit terminée.

Dans cette étape, je vais créer un ThreadExample classe qui s'étend de Thread et avoir deux run méthodes :

  • run() – annote avec @Override , il sera appelé par le start() du Thread méthode.
  • run(String msg) – c'est une méthode de classe normale. Il sera appelé lors de son invocation.

ThreadExample.java

package jcg.zheng.multithread.demo.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadExample extends Thread {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Override
	public void run() {
		logger.info(CommonUtil.THREAD_STARTED);
		CommonUtil.fiveSecondsProcess();
		logger.info(CommonUtil.THREAD_FINISHED);
	}

	public void run(String msg) {
		logger.info(" ** " + msg);
	}

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

		ThreadExample thread = new ThreadExample();
		thread.run("Mary : Hello !");
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());

		thread.start();
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());

		if (args.length == 1 && args[0].equalsIgnoreCase("wait")) {
			System.out.println("Wait!");
			CommonUtil.waitForThread(thread);
		}

		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
	}
}

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

C:\Users\aa00765\Desktop\Design_diagrams>java -jar TheadExample.jar wait
main Thread started
17:12:12.040 [main] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  ** Mary : Hello !
mainThread state = NEW
mainThread state = RUNNABLE
Wait!
17:12:12.048 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:12:17.051 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
mainThread state = TERMINATED
main Thread finished

C:\Users\aa00765\Desktop\Design_diagrams>java -jar TheadExample.jar
main Thread started
17:12:20.402 [main] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  ** Mary : Hello !
mainThread state = NEW
mainThread state = RUNNABLE
mainThread state = RUNNABLE
17:12:20.410 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
main Thread finished
17:12:25.416 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished

C:\Users\aa00765\Desktop\Design_diagrams>

5.2 Implémente Runnable

Semblable à l'implémentation précédente d'un thread, lorsqu'une classe implémente Runnable interface, il doit fournir sa propre implémentation de run() . Java 5 marque le Runnable interface avec @FunctionalInterface , nous pouvons donc utiliser Java 8 lambda pour créer un nouveau thread avec un Runnable .

Dans cette étape, je vais créer un RunnableThreadExample classe qui implémente le Runnable interface.

RunnableThreadExample.java

package jcg.zheng.multithread.demo.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class RunnableThreadExample implements Runnable {

 private Logger logger = LoggerFactory.getLogger(this.getClass());

 @Override
 public void run() {
 logger.info(CommonUtil.THREAD_STARTED);
 CommonUtil.fiveSecondsProcess();
 logger.info(CommonUtil.THREAD_FINISHED);
 }

 public static void main(String[] args) {
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

 Thread thread = new Thread(new RunnableThreadExample());
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());

 thread.start();
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());
 if (args.length == 1 && args[0].equalsIgnoreCase("wait")) {
 System.out.println("Wait!");
 CommonUtil.waitForThread(thread);
 }
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
 }

}

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

main Thread started
mainThread state = NEW
mainThread state = RUNNABLE
main Thread finished
17:15:08.822 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:15:13.844 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished

main Thread started
mainThread state = NEW
mainThread state = RUNNABLE
Wait!
17:15:41.740 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:15:46.751 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
main Thread finished

5.3 ThreadTestBase

Dans cette étape, je vais créer un ThreadTestBase qui testent la création, l'exécution et l'état d'un thread.

ThreadTestBase.java

package jcg.zheng.multithread.demo.thread;

import static org.junit.Assert.assertEquals;

import java.lang.Thread.State;

import org.junit.Test;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadTestBase {

	Thread thread;

	@Test
	public void create_start_finish() {
		assertEquals(State.NEW, thread.getState());

		assertEquals(State.NEW, thread.getState());

		thread.start();
		assertEquals(State.RUNNABLE, thread.getState());

		CommonUtil.waitForThread(thread);

		assertEquals(State.TERMINATED, thread.getState());
	}

	@Test(expected = IllegalThreadStateException.class)
	public void throw_exception_start_twice() {
		thread.start();
		thread.start();
	}

}

Comme vous l'avez vu dans le create_start_finish méthode, le thread nouvellement créé est dans le NEW état, puis passe à RUNNABLE par le start méthode, enfin, c'est en TERMINATED après qu'il soit terminé.

Comme vous l'avez vu dans le throw_exception_start_twice méthode, il lance un IllegalThreadStateExecption lors de l'appel de start() deux fois.

5.4 ThreadExampleTest

Dans cette étape, je vais créer un ThreadExampleTest . Au niveau de la méthode de configuration, il crée un Thread instance de ThreadExample .

ThreadExampleTest.java

package jcg.zheng.multithread.demo.thread;

import static org.junit.Assert.assertEquals;

import java.lang.Thread.State;

import org.junit.Before;

public class ThreadExampleTest extends ThreadTestBase{

	@Before
	public void setup() {
		thread = new ThreadExample();
		assertEquals(State.NEW, thread.getState());
		
		((ThreadExample)thread).run("Mary : Hello !");
		assertEquals(State.NEW, thread.getState());
	}

}

Exécutez mvn test -Dtest=ThreadExampleTest et capturez la sortie ici.

Sortie

Running jcg.zheng.multithread.demo.thread.ThreadExampleTest
17:19:25.769 [main] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  ** Mary : Hello !
17:19:25.781 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:19:30.796 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:19:30.802 [main] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  ** Mary : Hello !
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.38 sec
17:19:30.838 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  16.388 s
[INFO] Finished at: 2019-08-12T17:19:31-05:00
[INFO] ------------------------------------------------------------------------

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo>

5.5 RunnableThreadExampleTest

Dans cette étape, je vais un RunnableThreadExampleTest avec deux méthodes :

  • setup – créer un Thread instance de RunnableThreadExample
  • createWithLambda – créer un Thread instance avec la syntaxe Java 8 Lamdba

RunnableThreadExampleTest.java

package jcg.zheng.multithread.demo.thread;

import static org.junit.Assert.assertEquals;

import java.lang.Thread.State;

import org.junit.Before;
import org.junit.Test;

public class RunnableThreadExampleTest extends ThreadTestBase {

	@Before
	public void setup() {
		thread = new Thread(new RunnableThreadExample());
	}

	@Test
	public void createWithLambda() {
		thread = new Thread(() -> {
			System.out.println(("Hello from Lambda!"));
		});

		assertEquals(State.NEW, thread.getState());
		thread.start();
		assertEquals(State.RUNNABLE, thread.getState());
	}

}

Exécutez mvn test -Dtest=RunnableThreadExampleTest et capturez la sortie ici.

Sortie

Running jcg.zheng.multithread.demo.thread.RunnableThreadExampleTest
Hello from Lambda!
17:22:51.231 [Thread-2] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:22:56.238 [Thread-2] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.437 sec

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  13.272 s
[INFO] Finished at: 2019-08-12T17:22:56-05:00
[INFO] ------------------------------------------------------------------------

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo>

6. Détails du sujet

6.1 Priorité des threads

Chaque thread a une priorité qui aide le programme à déterminer l'ordre dans lequel les threads sont programmés. La priorité des threads est comprise entre MIN_PRIORITY (une constante de 1) et MAX_PRIORITY (une constante de 10). La priorité par défaut d'un thread est NORM_PRIORITY (une constante de 5). Les threads avec une priorité plus élevée sont exécutés avant les threads avec une priorité inférieure. Cependant, les priorités des threads ne garantissent pas l'ordre dans lequel les threads s'exécutent ensuite et cela dépend également de la plate-forme.

ThreadPriorityExample.java

package jcg.zheng.multithread.demo.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadPriorityExample extends Thread {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	public void run() {
		logger.info(CommonUtil.THREAD_STARTED);
		logger.info("Running Thread Priority: " + Thread.currentThread().getPriority());
		CommonUtil.fiveSecondsProcess();
		logger.info(CommonUtil.THREAD_FINISHED);
	}

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

		/* Thread Priority */
		ThreadPriorityExample minThread = new ThreadPriorityExample();
		minThread.setName("Thread1_MIN_PRIORITY");
		minThread.setPriority(Thread.MIN_PRIORITY);

		ThreadPriorityExample maxThread = new ThreadPriorityExample();
		maxThread.setName("Thread2_MAX_PRIORITY");
		maxThread.setPriority(Thread.MAX_PRIORITY);

		ThreadPriorityExample thread3 = new ThreadPriorityExample();
		thread3.setName("Thread3");

		System.out.println(Thread.currentThread().getName() + " starts with min, max, default priority order");
		minThread.start();
		maxThread.start();
		thread3.start();

		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
	}

}

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

main Thread started
main starts with min, max, default priority order
main Thread finished
17:24:59.573 [Thread2_MAX_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread started
17:24:59.580 [Thread2_MAX_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample - Running Thread Priority: 10
17:24:59.573 [Thread3] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread started
17:24:59.593 [Thread3] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample - Running Thread Priority: 5
17:24:59.573 [Thread1_MIN_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread started
17:24:59.594 [Thread1_MIN_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample - Running Thread Priority: 1
17:25:04.584 [Thread2_MAX_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread finished
17:25:04.594 [Thread3] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread finished
17:25:04.594 [Thread1_MIN_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread finished

6.2 Usine de threads

L'interface ThreadFactory définit un newThread(Runnable r) méthode pour créer un fil à la demande.

Dans cette étape, je vais créer un Thread à partir de ThreadFactory .

ThreadFactoryExample.java

package jcg.zheng.multithread.demo.thread;

import java.util.concurrent.ThreadFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadFactoryExample implements ThreadFactory {

 public static void main(String[] args) {
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

 ThreadFactoryExample tFactory = new ThreadFactoryExample("MZhengThreadFactory");
 for (int i = 0; i < 5; i++) {
 Thread thread = tFactory.newThread(new ThreadExample());
 thread.start();
 }
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
 }

 private int threadId;

 private String threadName;

 public ThreadFactoryExample(String name) {
 threadId = 1;
 this.threadName = name;
 }

 @Override
 public Thread newThread(Runnable r) {
 Thread thread = new Thread(r, threadName + "-Thread_" + threadId);
 threadId++;
 return thread;
 }

}

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

main Thread started
main Thread finished
17:26:52.681 [MZhengThreadFactory-Thread_2] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:52.684 [MZhengThreadFactory-Thread_3] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:52.685 [MZhengThreadFactory-Thread_1] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:52.681 [MZhengThreadFactory-Thread_5] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:52.684 [MZhengThreadFactory-Thread_4] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:57.724 [MZhengThreadFactory-Thread_1] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:26:57.724 [MZhengThreadFactory-Thread_2] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:26:57.724 [MZhengThreadFactory-Thread_3] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:26:57.725 [MZhengThreadFactory-Thread_4] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:26:57.725 [MZhengThreadFactory-Thread_5] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished

6.3 Service d'exécution

ExecutorService fournit un moyen indirect de créer un thread. Il gère un pool de threads, tous les threads du pool interne seront réutilisés.

Dans cette étape, je vais montrer comment obtenir un fil de ExecutorService .

ExecutorServiceExample.java

package jcg.zheng.multithread.demo.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import jcg.zheng.multithread.demo.CommonUtil;

public class ExecutorServiceExample {

 public static void main(String[] args) {
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

 ExecutorService service = Executors.newFixedThreadPool(5);

 for (int i = 0; i < 5; i++) {
 service.submit(new RunnableThreadExample());
 }

 service.submit(() -> {
 System.out.println(Thread.currentThread().getName() + " is reused");
 });

 try {

 // Executor must be stopped explicitly otherwise it keeps listens for new
 // tasks
 service.shutdown();
 service.awaitTermination(10l, TimeUnit.SECONDS);

 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {

 System.out.println(Thread.currentThread().getName() + " isTerminated = " + service.isTerminated());
 service.shutdownNow();
 }
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);

 }

}

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

main Thread started
17:28:15.344 [pool-1-thread-2] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:15.345 [pool-1-thread-1] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:15.344 [pool-1-thread-4] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:15.345 [pool-1-thread-3] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:15.344 [pool-1-thread-5] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:20.357 [pool-1-thread-2] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
17:28:20.357 [pool-1-thread-3] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
17:28:20.357 [pool-1-thread-5] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
17:28:20.357 [pool-1-thread-1] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
17:28:20.357 [pool-1-thread-4] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
pool-1-thread-4 is reused
main isTerminated = true
main Thread finished

Comme vous l'avez vu dans cet exemple, il a créé un pool de threads avec cinq threads. Le thread-4 est réutilisé pour la sixième requête.

6.4 Notification et attente de thread

Dans cette étape, je vais invoquer le notify() et wait() méthodes pour réveiller et mettre en pause le thread en cours.

Tout d'abord, je vais créer deux méthodes synchronisées :

  • printEven – il notifie le thread en cours si le nombre est pair et met en pause le thread en cours si le nombre est impair.
  • printOdd – il notifie le thread en cours si le nombre est impair et met en pause le thread en cours si le nombre est pair.

Deuxièmement, je vais créer un ThreadPrintOdd classe qui s'étend de Thread et invoque le printOdd() dans le run() méthode.

Troisièmement, je vais créer un Runnable interface via la syntaxe Java 8 lambda qui invoque printEven() méthode.

Enfin, je vais créer un main application qui crée oddThread et evenThreadJdk8 et démarre les deux threads. Comme le montre la sortie, ces deux threads se mettront en pause et s'exécuteront à tour de rôle.

ThreadNotifyWaitExample.java

package jcg.zheng.multithread.demo.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadNotifyWaitExample extends Thread {

 private static final class ThreadPrintOdd extends Thread {
 private final ThreadNotifyWaitExample tExample;

 private ThreadPrintOdd(ThreadNotifyWaitExample tExample) {
 this.tExample = tExample;
 }

 @Override
 public void run() {
 try {
 tExample.printOdd();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }

 public static void main(String[] args) {
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

 ThreadNotifyWaitExample tExample = new ThreadNotifyWaitExample();

 Thread oddThread = new ThreadPrintOdd(tExample);
 Thread evenThreadJdk8 = new Thread(printEven(tExample));

 oddThread.start();
 evenThreadJdk8.start();

 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);

 }

 private static Runnable printEven(ThreadNotifyWaitExample tExample) {
 return () -> {
 try {
 tExample.printEven();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 };
 }

 private Logger logger = LoggerFactory.getLogger(this.getClass());

 public synchronized void printEven() throws InterruptedException {
 for (int i = 0; i < 10; i++) {
 if (i % 2 == 0) {
 this.notify();
 logger.info("Even: " + i);
 } else {
 this.wait();
 }
 }
 }

 public synchronized void printOdd() throws InterruptedException {
 for (int i = 0; i < 10; i++) {
 if (i % 2 == 0) {
 this.wait();
 } else {
 this.notify();
 logger.info("Odd: " + i);
 }
 }
 }

}

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

main Thread started
main Thread finished
17:29:19.232 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 0
17:29:19.241 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 1
17:29:19.242 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 2
17:29:19.242 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 3
17:29:19.242 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 4
17:29:19.242 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 5
17:29:19.242 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 6
17:29:19.242 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 7
17:29:19.242 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 8
17:29:19.242 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 9

7. Multithreading en Java

Dans l'étape, je vais créer une application multithreading. Il a trois threads :

  • thread1 – instance de RunnableTheadExample
  • thread2 – instance de RunnableThreadExample
  • thread3 – instance de ThreadExample

Après avoir créé trois threads, thread1 et thread2 sont démarrés, alors,CommonUtil.waitForThread(thread1) est appelé pour mettre en pause le courant jusqu'à thread1 est fini. Après les deux thread1 et thread2 se termine, thread3 sera exécuté. Enfin, le main le thread se termine avant thread3 complète.

MultithreadsApp.java

package jcg.zheng.multithread.demo.thread;

import jcg.zheng.multithread.demo.CommonUtil;

public class MultiThreadsApp extends Thread {

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

		Thread thread1 = new Thread(new RunnableThreadExample());
		Thread thread2 = new Thread(new RunnableThreadExample());
		ThreadExample thread3 = new ThreadExample();

		thread1.start();
		thread2.start();

		CommonUtil.waitForThread(thread1);
		CommonUtil.waitForThread(thread2);

		thread3.start();

		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
	}

}

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

main Thread started
18:12:56.367 [Thread-1] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
18:12:56.367 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
18:13:01.384 [Thread-1] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
18:13:01.384 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
main Thread finished
18:13:01.386 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
18:13:06.386 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished

8. Synchronisation

La synchronisation est sur le point de contrôler l'accès des threads aux ressources partagées du programme. En Java, chaque objet a un verrou. Un thread peut acquérir le verrou d'un objet en utilisant synchronized mot-clé.

Le mot clé synchronized peut être implémenté au niveau de la méthode ou au niveau du bloc. Le niveau bloc est plus efficace que le niveau méthode car il ne verrouille pas toute la méthode.

8.1 Conditions de course

Dans cette étape, je vais créer une application Java simple pour démontrer la condition de concurrence dans une application multithread simultanée.

Le RaceConditionExample la classe a un membre mutable - count . Dans le raceConditionDemo méthode, il créera un pool de threads avec 5 threads et soumettra 10000 fois. Il devrait donc afficher 10000.

Comme vous le verrez dans la sortie, il s'imprime sous la forme 9987 en raison d'une condition de concurrence. Vous obtiendrez un nombre imprévisible qui ferme à 10000 pour chaque exécution.

RaceConditionExample.java

package jcg.zheng.multithread.demo.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

public class RaceConditionExample {

	private int count = 0;

	public int getCount() {
		return count;
	}

	AtomicInteger atomicCount = new AtomicInteger();
	public int getCountViaAtomicInteger() {
		count = atomicCount.incrementAndGet();
		return count;
	}

	public void increment() {
		count++;
	}

	public synchronized void incrementSyncMethod() {
		count++;
	}

	public void incrementSyncBlock() {
		synchronized (this) {
			count++;
		}
	}

	// mutual exclusion, same as synchronized
	ReentrantLock lock = new ReentrantLock();
	public void incrementLock() {
		lock.lock();
		try {
			count++;
		} finally {
			lock.unlock();
		}
	}

	public void raceConditionDemo(String type) {
		ExecutorService executor = Executors.newFixedThreadPool(5);
		for (int i = 0; i < 10000; i++) {
			switch (type) {
			case "RaceCondition":
				executor.submit(this::increment);
				break;
			case "Lock":
				executor.submit(this::incrementLock);
				break;
			case "SynchronizedBlock":
				executor.submit(this::incrementSyncBlock);
				break;
			case "SynchronizedMethod":
				executor.submit(this::incrementSyncMethod);
				break;
			case "AtomicInteger":
				executor.submit(this::getCountViaAtomicInteger);
				break;
			}
		}

		try {
			executor.awaitTermination(10l, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		executor.shutdown();
	}

	public static void main(String[] args) {
		RaceConditionExample rE = new RaceConditionExample();
		rE.raceConditionDemo(args[0]);
		System.out.println("Count = " + rE.getCount());
	}
}

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample RaceCondition
Count = 9987

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

8.2 Bloc synchronisé

Lorsque plusieurs threads accèdent aux mêmes objets mutables, nous devons synchroniser l'objet mutable pour éviter les données incorrectes dues à une erreur de condition de concurrence.

Veuillez faire référence au incrementSyncBlock méthode.

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample SynchronizedBlock
Count = 10000

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

8.3 Méthode synchronisée

La méthode xx est marquée par synchronized mot-clé. Je vais montrer comment l'utiliser pour éviter la condition de course

Veuillez faire référence au incrementSyncMethod méthode

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample SynchronizedMethod
Count = 10000

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

8.4 Verrouiller

Dans cette étape, je vais montrer comment utiliser ReentrantLock pour éviter la condition de concurrence.

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample Lock
Count = 10000

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

8.5 AtomicInteger

Dans cette étape, je vais montrer comment utiliser la classe AtomicInteger pour éviter les conditions de concurrence.

Exécutez-le en tant qu'application Java et capturez la sortie ici.

Sortie

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample AtomicInteger
Count = 10000

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

9. Résumé

Dans ce didacticiel, j'ai démontré la création de threads, l'état des threads, la priorité des threads, la fabrique de threads, le pool de threads et la synchronisation. Le java.util.concurrent L'API contient des utilitaires de haut niveau qui sont principalement utilisés dans la programmation simultanée. Voici les avantages et les inconvénients du multi-threading.

Avantages :

  • Meilleure utilisation des ressources système
  • Exécution parallèle des tâches et donc moins de temps d'exécution
  • Performances améliorées sur les machines multiprocesseurs
  • Amélioration de la réactivité de l'interface graphique
  • Threads indépendants (n'affectent pas les autres threads du même processus si une exception se produit)

Inconvénients :

  • Complexité du code
  • La synchronisation des ressources partagées (objets, données) est gourmande en CPU/mémoire
  • Le débogage est difficile car parfois vous ne pouvez pas prédire les résultats
  • Possibilité accrue d'occurrence de blocage
  • "Famine" certains fils de discussion peuvent ne pas être diffusés en raison d'une mauvaise conception

Il s'agissait d'un didacticiel pour le didacticiel Java multithreading.

Le didacticiel sur le multithreading en Java a été mis à jour pour la dernière fois le 14 août 2019

Balise Java