Correction de TD6: Gestion des processus, Les Sémaphores, et Serveurs multithreadés
Ce document couvre la gestion des processus en Java, l'utilisation des sémaphores pour la synchronisation, ainsi que la conception de serveurs multithreadés. Il s'adresse aux étudiants en informatique souhaitant comprendre la programmation concurrente et la gestion des ressources partagées.
D'après le document Correction de TD6: Gestion des processus, Les Sémaphores, et Serveurs multithreadés
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.
Document source
Concurrent Programming · PDF · 6 pages · 2015
Afficher l'aperçu du document
Ce document couvre la gestion des processus en Java, l'utilisation des sémaphores pour la synchronisation, ainsi que la conception de serveurs multithreadés. Il s'adresse aux étudiants en informatique souhaitant comprendre la programmation concurrente et la gestion des ressources partagées.
Gestion des processus
La gestion des processus en Java repose principalement sur la création et le contrôle des threads. Plusieurs exemples illustrent différentes manières de créer et d'exécuter des threads.
Exemple 1 : Création d'un thread en étendant la classe Thread
public class Compteur extends Thread {
public void run() {
// un thread permettant de compter de 1 à 10
for(int i = 1; i <= 10; i++) {
try {
System.out.println(this.getName() + i);
// pause de 1000 millisecondes
Thread.sleep((int)(java.lang.Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Compteur().start();
}
}
Ce programme crée un thread qui compte de 1 à 10 en affichant son nom suivi du numéro, avec une pause aléatoire entre chaque affichage.
Exemple 2 : Création d'un thread en implémentant l'interface Runnable
class Compteur2 implements Runnable {
static final int N = 10;
String name;
Compteur2(String n) {
name = n;
}
public void run() {
try {
for (int i = 1; i <= N; i++) {
Thread.sleep(10);
}
System.out.println(name);
} catch (Exception e) {
return;
}
}
public static void main(String[] args) {
Runnable compte1 = new Compteur2("Assma");
Runnable compte2 = new Compteur2("Alaa");
Runnable compte3 = new Compteur2("Youssef");
Runnable compte4 = new Compteur2("Ahmed");
new Thread(compte1).start();
new Thread(compte2).start();
new Thread(compte3).start();
new Thread(compte4).start();
}
}
Les threads démarrent presque simultanément, mais leur ordre d'exécution n'est pas garanti, car la JVM et le système d'exploitation gèrent l'ordonnancement.
Exemple 3 : Gestion des noms uniques pour les threads
public class Compteur3 implements Runnable {
static final int N = 10;
static class Node {
public String name;
public Node next;
public Node(String n) {
this.name = n;
this.next = null;
}
static Node knownNames;
}
boolean alreadyKnown(String name) {
if (Node.knownNames == null) {
Node.knownNames = new Node(name);
return false;
}
Node tmp = Node.knownNames;
while (tmp != null) {
if (tmp.name.equals(name))
return true;
tmp = tmp.next;
}
tmp = Node.knownNames;
while (tmp.next != null)
tmp = tmp.next;
tmp.next = new Node(name);
return false;
}
int valeur;
String name;
Compteur3(int val, String n) throws Exception {
if (alreadyKnown(n)) throw new Exception();
valeur = val;
name = n;
}
public void run() {
try {
for (int i = 1; i <= N; i++) {
Thread.sleep((int)(java.lang.Math.random() * 100));
}
System.out.println(name);
} catch (Exception e) {
return;
}
}
public static void main(String[] args) throws Exception {
Runnable compte1 = new Compteur3(10, "Ahmed");
Runnable compte2 = new Compteur3(10, "Assma");
Runnable compte3 = new Compteur3(10, "Ahmed"); // Exception levée ici
Runnable compte4 = new Compteur3(10, "Youssef");
new Thread(compte1).start();
new Thread(compte2).start();
new Thread(compte3).start();
new Thread(compte4).start();
}
}
Ce programme empêche la création de threads avec des noms déjà utilisés, en lançant une exception si un nom est dupliqué.
Exemple 4 : Ordre d'achèvement des threads
public class CompteurX extends Thread {
private String nom;
private int max;
private static int ordre = 1;
public CompteurX(String nom, int max) {
this.nom = nom;
this.max = max;
}
public void run() {
for (int i = 1; i <= max; i++) {
try {
sleep((int)(Math.random() * 100));
} catch (InterruptedException e) {
System.err.println(nom + " a été interrompu.");
}
System.out.println(nom + " : " + i);
}
System.out.println("*** " + nom + " a terminé en position " + ordre++);
}
public static void main(String[] args) {
Runnable compte1 = new CompteurX("Ahmed", 10);
Runnable compte2 = new CompteurX("Assma", 10);
Runnable compte3 = new CompteurX("Alaa", 10);
Runnable compte4 = new CompteurX("Youssef", 10);
new Thread(compte1).start();
new Thread(compte2).start();
new Thread(compte3).start();
new Thread(compte4).start();
}
}
Chaque thread affiche sa progression et indique sa position d'achèvement grâce à un compteur statique partagé.
Les Sémaphores
Les sémaphores sont des mécanismes de synchronisation permettant de contrôler l'accès concurrent à des ressources partagées. Ils fonctionnent comme un compteur (permits) accessible uniquement via deux opérations atomiques : acquire (P) et release (V).
L'opération acquire décrémente le compteur et bloque si celui-ci est à zéro, tandis que release l'incrémente et réveille éventuellement un thread en attente.
Exemple 1 : Implémentation d'un sémaphore borné
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound) {
bound = upperBound;
}
public synchronized void acquire() throws InterruptedException {
while (signals == bound)
wait();
signals++;
notify();
}
public synchronized void release() throws InterruptedException {
while (signals == 0)
wait();
signals--;
}
}
Ce sémaphore permet de limiter le nombre de threads accédant simultanément à une ressource.
Exemple 2 : Utilisation d'un sémaphore pour l'exclusion mutuelle
import java.util.concurrent.Semaphore;
public class essaiPV extends Thread {
static int x = 3;
Semaphore u;
public essaiPV(Semaphore s) {
u = s;
}
public void run() {
int y;
// u.acquire();
try {
Thread.currentThread().sleep(100);
y = x;
Thread.currentThread().sleep(100);
y = y + 1;
Thread.currentThread().sleep(100);
x = y;
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + ": x=" + x);
// u.release();
}
public static void main(String[] args) {
Semaphore X = new Semaphore(1);
new essaiPV(X).start();
new essaiPV(X).start();
}
}
Sans l'utilisation effective des méthodes acquire() et release() (commentées ici), deux threads peuvent accéder simultanément à la variable partagée x, provoquant une condition de course et une mise à jour incorrecte.
Serveurs multithreadés
Le multithreading améliore la performance et la réactivité des serveurs en permettant de gérer plusieurs connexions simultanément. Un serveur multithreadé peut accepter de nouvelles connexions même lorsqu'il traite déjà d'autres requêtes.
Exemple : Serveur TCP multithreadé
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public final class TcpServer extends Thread {
private ServerSocket serverSocket;
private int portNumber = 4000;
public TcpServer() throws IOException {
setName("TcpServer");
serverSocket = new ServerSocket(portNumber);
}
public void run() {
try {
while (!isInterrupted()) {
// accept a new connection
Socket socket = serverSocket.accept();
// create a new thread to handle communication with the remote client
new ConnectionHandler(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
TcpServer tcpServer = new TcpServer();
tcpServer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Gestion des connexions clients
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;
public final class ConnectionHandler extends Thread {
private Socket socket;
public ConnectionHandler(Socket socket) {
setName("ConnectionHandler");
this.socket = socket;
}
public void run() {
BufferedReader in = null;
PrintWriter out = null;
String request;
try {
// Get the input and output streams
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
while ((request = in.readLine()) != null) {
if (request.equals("GET TIME"))
out.println(new Date().toString());
else if (request.equals("GET JAVA VERSION"))
out.println(System.getProperty("java.version"));
else if (request.equals("CLOSE"))
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Chaque connexion client est gérée par un thread distinct qui traite les requêtes comme "GET TIME", "GET JAVA VERSION" ou "CLOSE".
Glossaire des termes clés
- Thread : Unité d'exécution légère permettant l'exécution concurrente dans un programme.
- Runnable : Interface fonctionnelle en Java utilisée pour définir le code à exécuter dans un thread.
- sleep() : Méthode statique qui suspend l'exécution du thread courant pendant un temps donné.
- InterruptedException : Exception levée lorsqu'un thread en sommeil est interrompu.
- Sémaphore : Mécanisme de synchronisation qui contrôle l'accès concurrent à une ressource partagée via un compteur.
- acquire (P) : Opération atomique sur un sémaphore qui décrémente le compteur et bloque si celui-ci est nul.
- release (V) : Opération atomique sur un sémaphore qui incrémente le compteur et réveille un thread en attente.
- Exclusion mutuelle : Propriété garantissant qu'une ressource partagée n'est utilisée que par un seul thread à la fois.
- Serveur multithreadé : Serveur capable de gérer plusieurs connexions simultanément grâce à des threads distincts.
- Socket : Point de communication réseau entre deux machines.
Points clés à retenir
- Les threads peuvent être créés en étendant la classe
Threadou en implémentant l'interfaceRunnable. - L'ordonnancement des threads est non déterministe et dépend de la JVM et du système d'exploitation.
- Les sémaphores permettent de gérer l'accès concurrent à des ressources partagées et d'assurer l'exclusion mutuelle.
- Un sémaphore binaire agit comme un verrou simple, tandis qu'un sémaphore borné peut contrôler plusieurs accès simultanés.
- Les serveurs multithreadés améliorent la réactivité en traitant chaque connexion client dans un thread séparé.
- La gestion correcte des interruptions et exceptions est essentielle dans la programmation concurrente.
Commentaires
Aucun commentaire pour le moment. Posez la première question.