Sémaphore
Un sémaphore est un objet spécial qui aide à la synchronisation des threads et est un complément au mot clé synchronized
. Les sémaphores sont des permis d'exécutions. Seule une quantité de permis sera disponible et le thread devra attendre qu'un permis soit libéré avant de continuer.
On peut voir un parallèle avec les ExecutionService
, où N
threads pouvaient s'exécuter simultanément. Avec un sémaphore, c'est le même principe, mais avec un bout de code.
Pour utiliser un sémaphore, on doit commencer par le créer.
Par la suite, on pourra obtenir un permis et les retourner :
// Obtenir un permis - le code attendra si aucun n'est disponible
semaphore.acquire();
// Exécuter du code
Thread.sleep(5000);
// Retourner le permis
semaphore.release();
Exemple complet utilisant un sémaphore
Socrate entre dans le salon.
Socrate veut parler.
Platon entre dans le salon.
Platon veut parler.
Platon parle.
Aristote entre dans le salon.
Aristote veut parler.
Sartre entre dans le salon.
Sartre veut parler.
Descartes entre dans le salon.
Descartes veut parler.
Socrate parle.
Platon a terminé de parler.
Aristote parle.
Aristote a terminé de parler.
Sartre parle.
Socrate a terminé de parler.
Descartes parle.
Sartre a terminé de parler.
Descartes a terminé de parler.
App.java
ligne 6: Le sémaphore de 2 peut représenter deux bâtons de paroles. Seulement deux personnes à la fois pourront parler.Philosophe.java
ligne 31: Lorsque le thread est lancé, le philosophe indique qu'il arrive dans le salon.Philosophe.java
ligne 20: Le philosophe désire prendre la parole. Pour se faire, il aura besoin d'obtenir un permis, ce qu'il tente d'obtenir à la ligne 21.Philosophe.java
lignes 22-24: Le philosophe parle pour une durée de 1 à 4 secondes.Philosophe.java
ligne 25: Le philosophe a terminé de parler et retourne son bâton de parole. Un autre philosophe pourra alors le prendre.Console
: En regardant de près, on peut voir que tous les philosophes entrent dans le salon et veulent parler. Cependant, seulement 2 peuvent parler en même temps.
Quelques méthodes des sémaphores
Méthode | Retour | Description |
---|---|---|
availablePermits() |
int |
Retourne la quantité de permis disponibles. |
acquire() |
void |
Tente d'obtenir un permis. Bloque l'exécution du code. |
tryAcquire() |
boolean |
Tente d'obtenir un permis et retourne le succès. Ne bloque pas l'exécution du code. |
release() |
void |
Rend un permis disponible à nouveau. |
getQueueLength() |
int |
Retourne le nombre de threads en attente d'un permis. |
Mises en garde
Quand on joue avec des locks, il faut faire attention à ne pas causer d'attente infinie, c'est à dire qu'un thread attend après une ressource qui ne sera jamais disponible. Le thread ne pourra donc jamais continuer. Imaginons le thread suivant :
@Override
public void run() {
try {
System.out.println("Premier acquire");
this.semaphore.acquire();
Thread.sleep(1000);
System.out.println("Second acquire");
this.semaphore.acquire();
Thread.sleep(1000);
System.out.println("Premier release");
this.semaphore.release();
System.out.println("Second release");
this.semaphore.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
et le code d'application suivant :
Semaphore semaphore = new Semaphore(1);
Thread thread = new Thread(new ThreadDeadlock(semaphore));
System.out.println("Démarrage du thread");
thread.start();
thread.join();
System.out.println("Fin de l'application");
Le sémaphore ne possède qu'un seul permis, mais on tente d'en obtenir deux dans le thread. C'est un état impossible, le thread devra donc attendre indéfiniment.
Un second exemple, celui-ci à plusieurs threads, peut subvenir chacun des threads attend après l'autre thread pour libérer sa ressource. Par exemple :
@Override
public void run() {
try {
// Afficher un message
this.writer.println("Bienvenue!");
// Obtenir un permis
this.semaphore.acquire();
// Demander un nombre à doubler
this.writer.print("Veuillez entrer un nombre: ");
this.writer.flush();
int nombre = Integer.parseInt(this.reader.readLine());
int nombreDouble = nombre * 2;
// Le travail est terminé, retourner le permis
this.semaphore.release();
// Afficher le résultat
this.writer.printf("%d x 2 = %d", nombre, nombreDouble);
// Fermer la connexion
this.socket.close();
} catch (InterruptedException | IOException e) {
throw new RuntimeException(e);
}
}
- Un cilent se connecte via un socket
- Sur le serveur, un permis de sémaphore est utilisé pour traiter le client
- Le travail est effectué
- Le client se déconnecte et le serveur libère le permis
Tout semble bien aller. Cependant, si une erreur arrive (ex. du texte est entré au lieu d'un nombre), le permis ne sera jamais retourné. Une fois tous les permis du sémaphore épuisé, aucune connexion ne sera plus possible.
Les deadlocks
Un deadlock est un autre exemple d'attente infinie. Celui-ci est causé quand plusieurs threads différents peuvent verrouiller plusieurs permis (locks). Une situation pourrait arriver où un thread attend après un lock pour continuer, mais que la libération de ce lock dépend de la fin du thread. Le premier attend après l'autre et l'autre attend après le premier.