Les blocs synchronisés impliquent une exclusion mutuelle. À tout moment, un seul thread est autorisé à détenir le verrou et à exécuter le code dans un bloc synchronisé. Cette règle s'étend à tous les blocs gardés par le même verrou.
Dans votre cas, il y a deux blocs de ce type qui utilisent le même verrou, donc c'est soit le fil principal, soit le fil secondaire. MyThread
qui est autorisé à exécuter du code dans l'un de ces blocs, l'autre thread doit attendre. Ainsi, vous avez ici le scénario suivant :
- Le fil principal acquiert le verrou.
- Le fil principal lance le deuxième fil.
- Le second thread atteint le bloc synchronisé mais ne peut pas y entrer car le verrou est maintenu par le thread principal.
- Le fil principal appelle
wait()
. Cet appel libère le verrou et place le fil d'exécution principal dans l'état d'urgence. WAITING
l'État.
- Le deuxième thread peut maintenant acquérir le verrou et entrer dans le bloc synchronisé.
- Le deuxième fil compte jusqu'à cinq et appelle
notify()
. Cet appel ne libère pas le verrou, il informe simplement le thread principal qu'il peut avancer dès qu'il peut réacquérir le verrou.
- Le thread principal se réveille, mais il ne peut pas progresser car il ne peut pas réacquérir le verrou (il est toujours détenu par le second thread). Rappelez-vous, deux threads ne peuvent pas être actifs à la fois dans un bloc synchronisé gardé par le même verrou, et maintenant, le deuxième thread est toujours actif, donc le thread principal doit continuer à attendre.
- Le deuxième fil continue à compter, met
times
à 10 et quitte finalement le bloc synchronisé, libérant le verrou.
- Le fil d'exécution principal récupère le verrou et peut à présent progresser vers l'étape suivante.
println
. Mais à ce moment-là, le times
est déjà de 10.
Utilisation de join()
ne vous aidera pas non plus car le résultat sera le même - le thread principal ne pourra progresser que lorsque le second sera terminé.
Si vous voulez que votre thread principal continue l'exécution dès que le second thread frappe 5
vous devez acquérir le verrou et le libérer immédiatement après cet événement :
public class MyThread extends Thread {
public volatile int times = 0;
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
times += 1;
Thread.sleep(500);
if (i == 5) {
synchronized(this) {
this.notify();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
N'oubliez pas de faire times
volatile, sinon la JVM ne garantira pas que vous verrez sa valeur réelle dans votre thread principal.
Vous devez également comprendre que cette approche ne garantit pas que votre thread principal imprime 5. Il se peut qu'au moment où il atteigne la fonction println
le second thread effectue une ou deux itérations, voire plus, et vous verrez quelque chose de plus grand que 5 (bien que ce soit très malencontreux en raison de l'utilisation de l'option sleep()
à chaque itération).