Contenu du cours
Multithreading en Java
Multithreading en Java
Verrou et Condition
En Java, la synchronisation standard est basée sur le mot-clé synchronized
et les objets moniteurs intégrés. Cependant, dans certains cas, synchronized peut ne pas être suffisant, surtout lorsque plus de flexibilité dans la gestion des threads est requise.
Description Générale
L'interface Lock
et l'interface Condition
, introduites dans le package java.util.concurrent.locks
, fournissent des capacités avancées de gestion des threads.
Dans cette image, vous pouvez voir que le premier thread capture le verrou en utilisant la méthode lock()
, et à ce moment-là, un autre thread ne peut pas capturer le même verrou. Dès que tout le code à l'intérieur du verrou est exécuté, il appellera la méthode unlock()
et libérera le verrou. Ce n'est qu'après cela que le deuxième thread pourra capturer le verrou.
Différence
La différence entre ces deux interfaces est que les implémentations de Lock
sont une alternative de haut niveau au bloc synchronisé, et les implémentations de l'interface Condition
sont une alternative aux méthodes notify()
/wait()
. Ces deux interfaces font partie du package java.util.concurrent.locks
.
Exemples de la vie réelle
Imaginez que vous gérez une file d'attente pour l'inscription à un événement. Pour éviter le dépassement et assurer une allocation correcte des sièges, vous devez utiliser des mécanismes de blocage et des conditions pour maintenir le flux de nouvelles inscriptions en attente jusqu'à ce qu'un siège libre soit disponible.
Classe ReentrantLock
La classe ReentrantLock
du package java.util.concurrent.locks
est une implémentation de l'interface Lock
. Elle fournit des fonctionnalités pour gérer explicitement les verrous.
Les principales méthodes de ReentrantLock :
lock()
: Capture un verrou;unlock()
: Libère le verrou;tryLock()
: Tente de capturer le verrou et retourne vrai si la capture est réussie;tryLock(long timeout, TimeUnit unit)
: Tente de capturer le verrou pendant le temps spécifié;newCondition()
: Crée une condition pour leLock
actuel.
Main
package com.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { // Creating a ReentrantLock object private final Lock lock = new ReentrantLock(); private int count = 0; // Method to increment the `count` variable public void increment() { lock.lock(); // Acquiring the `lock` try { count++; System.out.println("Count incremented to: " + count); } finally { lock.unlock(); // Releasing the `lock` } } public static void main(String[] args) { Main example = new Main(); // Runnable task to call the increment method Runnable task = example::increment; // Starting multiple threads to execute the task for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }
Comme vous pouvez le voir, dans la méthode increment()
nous utilisons le verrouillage avec Lock
. Lorsqu'un thread entre dans la méthode, il capture le verrou avec lock.lock()
puis exécute le code et ensuite dans le bloc finally nous libérons le verrou avec lock.unlock()
afin que d'autres threads puissent entrer.
Nous libérons le verrou dans le bloc finally
pour une raison, c'est simplement que ce bloc est presque toujours exécuté, même avec des exceptions, sauf lorsque nous terminons le programme.
Interface Condition
Nous pouvons seulement créer une Condition
avec un lien vers une implémentation spécifique de Lock
. Pour cette raison, les méthodes de Condition
n'affecteront que le verrouillage de cette implémentation particulière de Lock
.
Main
private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();
Les principales méthodes de Condition :
await()
: Attend un signal d'un autre thread ;signal()
: Débloque un thread en attente sur une condition ;signalAll()
: Débloque tous les threads en attente sur la condition.
Main
private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; public void waitForCondition() throws InterruptedException { lock.lock(); // Acquire the `lock` try { while (!ready) { // Check if the condition is not met System.out.println("Waiting..."); // Print a waiting message condition.await(); // Wait for the condition to be signaled } System.out.println("Condition met!"); // Print a message when the condition is met } finally { lock.unlock(); // Release the `lock` } }
La méthode waitForCondition()
bloque le thread jusqu'à ce que la variable ready
devienne true, ce qui indique que la condition a été remplie. Lorsque la condition est remplie, le thread continue de s'exécuter, affichant le message “Condition remplie !”
Remarque
Lorsque la méthode
await()
est appelée, le thread est suspendu et libère également le verrou qu'il a capturé. Lorsque le thread se réveille, il doit capturer le verrou à nouveau et seulement alors il commencera à s'exécuter !
Exemple de Code
Voyons maintenant un exemple d'utilisation de ReentrantLock
et Condition
pour gérer l'inscription à un événement :
Un court extrait de la vidéo
Verrouillage avec ReentrantLock : La méthode register()
capture le verrou avec lock.lock()
pour empêcher plusieurs threads d'exécuter le code en même temps.
Condition avec Condition : S'il n'y a pas d'espaces disponibles, le thread appelle spaceAvailable.await()
pour attendre jusqu'à ce qu'un espace soit disponible.
Libérer le verrou : Lorsqu'un thread a libéré de l'espace en utilisant la méthode cancel()
, il appelle spaceAvailable.signalAll()
pour notifier tous les threads en attente.
Gestion des exceptions : L'utilisation des blocs try-finally
garantit que le verrou est libéré même si une exception se produit.
Conclusion
L'utilisation de
Lock
etCondition
en Java permet un contrôle plus flexible des threads et de la synchronisation que le mécanisme synchronisé traditionnel. Cela est particulièrement utile dans des scénarios complexes où un contrôle plus précis des threads et des conditions d'attente est requis.
Merci pour vos commentaires !