Scheduler
Maintenant qu'on a vu les différents états possible pour un thread, on va pouvoir mieux comprendre pourquoi on distingue les états Runnable et Running. Le but des threads est de pouvoir être exécutés en parallèle, mais pour cela, il faut bien entendu autant de machines ou de processeurs qu'il y a de threads à exécuter, ce qui n'est pas toujours possible. Que se passe-t-il alors ? La machine virtuelle Java possède un scheduler (ou ordonnanceur en français) de thread qui va faire en sorte de répartir les ressources processeurs le plus équitablement possible entre les différents threads.
Fonctionnement
Voyons en gros le fonctionnement d'un scheduler. Une vue globale vous est montrée sur la figure suivante. On peut y voir trois éléments : un pool de thread, le scheduler et les processeurs.

Supposons que l'on dispose de n processeurs. À tout moment, il peut y avoir au maximum n threads qui sont dans l'état Running. Tous les autres threads qui sont prêts à être exécutés se trouvent dans l'état Runnable et sont stockés dans le pool de threads. Le boulot du scheduler est de faire bouger les threads entre le pool de threads et les processeurs et vice-versa, afin de garantir que tout thread qui peut l'être sera exécuté.
Une version naive d'algorithme de scheduling consiste à garder, pour chaque thread, le moment où il a pu sortir du pool de threads vers un processeur. On fixe ensuite une limite de temps par thread, et une fois ce dernier écoulé, on ramène le thread dans le pool et on sélectionne un autre à envoyer vers le processeur ainsi libéré. On ne va pas entrer dans les détails des techniques d'ordonnancement, mais cette solution n'est pas très intéressante en général.
Exemple
Voyons un exemple concret grâce auquel vous allez pouvoir observer qu'il y a bel et bien un scheduler. La classe suivante représente un thread qui effectue un décompte de 3 à 1.
|
|
NamedCountDown : Illustration du fonctionnement du scheduler.Écrivons maintenant un programme qui crée trois threads à partir de cette classe et qui les exécute, c'est-à-dire appelle la méthode start sur ces instances. On va également donner un nom à ces trois threads, par exemple Alice, Bob et Charles. Voici un résultat possible après exécution du programme :
Alice : 3 Bob : 3 Alice : 2 Bob : 2 Charles : 3 Alice : 1 Bob : 1 Charles : 2 Charles : 1
Ce résultat nous montre bien que chaque thread n'a pas été exécuté d'un seul coup. Il y a eu de l'interleaving, c'est-à-dire que le thread a été voyagé par le scheduler entre le processeur et le pool de threads. Pour comprendre ce qui a pu se passer, supposons qu'on dispose d'une machine avec un seul processeur. La figure suivante vous quel thread est en cours d'exécution sur le processeur au cours du temps.

On voit donc que le premier thread a être exécuté est le main-thread, ce qui est tout à fait normal. Dans cet exemple, les deux premières instructions de la méthode ont été exécutées et les threads Alice et Bob sont ainsi créés et vont vers l'état Runnable. À ce moment précis, le scheduler décide de suspendre le main-thread, c'est-à-dire de le passer dans l'état Runnable et sélectionne Alice pour passer en Running. La première itération de la boucle s'exécute et le thread écrit donc Alice : 3 sur la sortie standard.
Vous pouvez aisément suivre la suite de l'histoire grâce à la figure 1.6 et à ce qui a été écrit sur la sortie standard après exécution du programme. On voit qu'après Alice, ce sera au tour de Bob, puis à nouveau Alice suivi de Bob à être exécuté. Après cela, le scheduler choisit le main-thread qui va pouvoir créer le troisième thread Charles et passe donc dans l'état Dead puisqu'il n'y a plus aucune autre instruction dans la méthode main. Charles est directement sélectionné par le scheduler et sera suivi par Alice et Bob qui exécuteront chacun leur dernière instruction. Pour terminer, il ne reste plus que Charles qui va donc clôturer l'exécution de ce programme.
Vous vous demandez sans doute pourquoi on a fait un sleep dans la boucle for de la méthode run de la classe NamedCountDown. Si on ne l'avait pas fait, le scheduler n'aurait jamais pu agir. En effet, le corps de la méthode run n'est pas très long à exéuter et chaque thread aurait pu rester sur le processeur durant toute sa vie. En ajoutant un sleep, même de zéro millisecondes, on force en fait le thread à passer de l'état Running à Runnable via l'état Blocked/Waiting (rappelez-vous de la section précédente). Il s'agit donc juste d'une astuce nous permettant d'observer le travail du scheduler.
Influencer le scheduler
On peut influencer le scheduler grâce à diverses méthodes de la classe Thread. Il faut néanmoins faire attention avec ces différentes méthodes, elle ne fournissent en général aucune garantie. Il faut plutôt voir ces méthodes comme des requêtes que le scheduler est libre d'exécuter ou non.
La méthode de classe yield permet d'être gentil et de faire passer un thread de l'état Running vers l'état Runnable. Après appel de la méthode, le thread courant va être suspendu, ce qui offre aux autres thread une chance d'être choisis par le scheduler pour pouvoir être exécutés.
public static void yield();
Chaque thread possède également une priorité. Un thread avec une plus haute priorité aura plus de chance d'être choisi pour être exécuté et passera, en théorie, plus de temps sur le processeur. On peut connaitre la priorité et la modifier grâce à des méthodes de la classe Thread :
public final int getPriority(); public final void setPriority (int newPriority);
La priorité d'un thread est une entier int compris entre les constantes de classe Thread.MIN_PRIORITY et Thread.MAX_PRIORITY. En règle générale, lorsque vous tentez d'augmenter la priorité d'un thread, cela n'aura pas d'effet, ce n'est que lorsque vous diminuerez sa priorité que ce sera pris en compte.
Pour résumer un peu l'influence qu'on peut avoir sur le scheduler, en général, lorsqu'on va vouloir être gentil et libérer des ressources, ce sera accepté et pris en compte. Par contre, lorsqu'on veut s'accaparer des ressources, nos requêtes ne seront en général pas prises en compte.





















