« Bonjour, Amigo ! Nous avons une panacée : un remède à tous les maux. Comme nous l'avons déjà vu, le basculement non contrôlé entre threads est un problème. »

« Pourquoi les threads ne pourraient pas décider eux-mêmes quand passer à un autre ? Pourquoi ne pas les laisser faire ce qu'ils veulent et nous dire quand ils ont fini ? »

« Permettre aux threads eux-mêmes de contrôler le basculement serait un problème encore plus grand. Supposons que nous avons du code mal écrit, et que le thread ne rend jamais le processeur. De mon temps, c'était comme ça que les choses fonctionnaient. Et c'était un véritable cauchemar. »

« OK. Alors quelle est la solution ? »

« On bloque les autres threads. C'est comme cela que ça fonctionne. »

Il est devenu clair que les threads interféraient entre eux lorsqu'ils essayaient d'utiliser des ressources et/ou objets partagés. Exactement comme nous l'avons vu dans l'exemple avec la sortie de la console : il y a une seule console et tous les threads écrivent dessus. C'est désordonné.

Alors on a inventé un objet spécial : le mutex. C'est comme un panneau sur la porte de la salle de bains qui dirait 'libre/occupé'. Le mutex a deux états : l'objet est disponible ou occupé. Ces états sont aussi appelés 'verrouillé' et 'déverrouillé'.

Quand un thread a besoin d'un objet qu'il partage avec d'autres threads, il vérifie le mutex associé à l'objet. Si le mutex est déverrouillé, le thread le verrouille (le marque comme 'occupé') et commence à utiliser la ressource partagée. Une fois que le thread a terminé son activité, le mutex est déverrouillé (marqué comme 'disponible').

Si le thread veut utiliser l'objet et que le mutex est verrouillé, alors le thread attend. Lorsque le mutex est finalement déverrouillé par le thread occupant, notre thread le verrouille immédiatement et commence à s'exécuter. L'analogie avec un panneau sur la porte de la salle de bains est parfaite.

« Alors comment je travaille avec un mutex ? Je dois créer des objets spéciaux ? »

« C'est beaucoup plus simple que cela. Les créateurs de Java ont intégré ce mutex dans la classe Object. Tu n'as même pas besoin de le créer. Il fait partie de chaque objet. Voici comment cela fonctionne : »

Code Description
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
La méthode swap échange les valeurs des variables name1 et name2.

Qu'est-ce qui pourrait arriver si elle était appelée à partir de deux threads en même temps ?

Exécution réelle du code Code du premier thread Code du second thread
String s1 = name1; //Ally
name1 = name2; //Lena
name2 = s1; //Ally

String s2 = name1; //Lena
name1 = name2; //Ally
name2 = s2; //Lena
String s1 = name1;
name1 = name2;
//other thread is running
name2 = s1;
//the thread waits until the mutex is unlocked

String s2 = name1;
name1 = name2;
//other thread is running
//other thread is running
name2 = s2;
Conclusion
Les valeurs des variables ont été échangées deux fois, revenant à leur emplacement d'origine.

Remarque le mot-clé synchronized.

« Oui, qu'est-ce qu'il veut dire ? »

« Quand un thread pénètre dans un bloc de code marqué synchronized, la machine Java verrouille immédiatement le mutex de l'objet indiqué entre parenthèses après le mot synchronized. Aucun autre thread ne peut entrer dans ce bloc avant que notre thread le quitte. Dès que notre thread quitte le bloc marqué synchronized, le mutex est immédiatement et automatiquement déverrouillé et devient disponible à l'acquisition pour les autres threads. »

Si le mutex est occupé, notre thread attend gentiment qu'il se libère.

« C'est si simple et élégant. Quelle belle solution. »

« Oui. Mais à ton avis, que se passera-t-il dans ce cas ? »

Code Description
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public void swap2()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
Les méthodes swap et swap2 partagent le même mutex (l'objet this).

Que se passe-t-il si un thread appelle la méthode swap et un autre thread appelle la méthode swap2 ?

« Étant donné que le mutex est le même, le second thread devra attendre que le premier quitte le bloc synchronisé. Donc il n'y aura pas de problèmes d'accès simultané. »

« Bien joué, Amigo ! C'est la bonne réponse ! »

Je tiens également à souligner que synchronized peut être utilisé pour marquer non seulement des blocs de code, mais aussi des méthodes. Voici ce que cela signifie :

Code Ce qui se passe vraiment
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public synchronized void swap()
{
String s = name1;
name1 = name2;
name2 = s;
}

public static synchronized void swap2()
{
String s = name1;
name1 = name2;
name2 = s;
}
}
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public static void swap2()
{
synchronized (MyClass.class)
{
String s = name1;
name1 = name2;
name2 = s;
}
}