Pour comprendre la programmation concurrente, imaginez une cuisine. Un programme classique, dit mono-thread, est comme un cuisinier seul : il prépare l'entrée, puis le plat, puis le dessert, une tâche après l'autre. Un programme multi-thread, lui, est comme une équipe de cuisiniers. Ils travaillent en parallèle, chacun sur une partie de la recette, ce qui permet de préparer le repas beaucoup plus rapidement. Ces "cuisiniers" virtuels sont ce que nous appelons des threads.
Cependant, faire travailler plusieurs cuisiniers dans la même cuisine introduit des défis. Si deux cuisiniers essaient d'utiliser le même pot de sel en même temps, le chaos peut s'installer. En programmation, c'est exactement le même principe. Les threads offrent une puissance considérable, mais ils comportent aussi des risques.
Pour bien comprendre ce péril, nous allons maintenant explorer un cas pratique simple mais révélateur : la classe Box. Nous allons observer directement comment deux threads, nos deux "cuisiniers", peuvent créer un désordre complet en manipulant une "boîte" partagée, et surtout, comment nous pouvons y remettre de l'ordre.
Cette promesse de performance et les risques qu'elle engendre nous amènent à notre scénario d'expérimentation. Pour notre expérience, nous avons besoin de trois éléments : un objet partagé, des "travailleurs" (threads) qui vont le manipuler, et un programme principal pour lancer le tout.
BoxVoici notre "ingrédient" commun. C'est une simple boîte numérique qui contient une seule valeur, val. On peut lire cette valeur avec la méthode get() et l'augmenter avec increment().
public class Box {
public int val;
public Box() {
val = 0;
}
public int get() {
return val;
}
public void increment(int incVal) {
val += incVal;
System.out.println("after inc : " + val);
}
}
ThreadIncSharedChaque instance de cette classe est un "travailleur". Sa mission, définie dans la méthode run(), est simple : lire la valeur actuelle de la Box, l'afficher, puis l'incrémenter. Il répète cette opération cinq fois. Le point crucial est que chaque thread reçoit une référence vers la même et unique instance de Box. Il est important de noter la différence entre la variable b.val (la donnée partagée dans la Box) et la variable locale val dans la méthode run(), qui n'est qu'une copie temporaire de la valeur à un instant T.
public class ThreadIncShared extends Thread {
public int id, incVal;
public Box b;
public ThreadIncShared(int id, int incVal, Box b) {
this.id = id;
this.incVal = incVal;
this.b = b;
}
public void run() {
System.out.println("I am thread " + id);
int val;
for(int i=0; i<5; i++) {
val = b.get();
System.out.println("T" + id + " - " + val);
b.increment(incVal);
}
System.out.println("I die ");
}
}
TestThreadIncSharedCe code met en place notre scène. Il crée une seule Box, puis deux threads, t1 et t2.
t1 aura pour mission d'ajouter 2 à chaque boucle.t2 aura pour mission d'ajouter 5 à chaque boucle.Ensuite, il les lance simultanément avec la méthode start().
public class TestThreadIncShared {
public static void main(String[] args) {
Box b = new Box();
ThreadIncShared t1,t2;
t1 = new ThreadIncShared(1,2,b);
t2 = new ThreadIncShared(2,5,b);
t1.start();
t2.start();
}
}