1. Ordonnancement des tâches JAVA
1.1. Généralité
La notion d'ordonnancement permet de voir clairement la synchronisation. Le choix d'un thread JAVA à exécuter dans un programme (au moins partiellement) se fait parmi les threads qui sont prêts. Si on est sur un système (machine) monoprocesseur, un seul thread peut être actif à un moment donné. Dans JAVA on a un ordonnanceur préemptif, qui est basé sur la priorité des processus: cela veut dire que l'ordonnanceur essaie de rendre actif (si on se place d'abord dans le cas simple où il n'y a pas deux threads de même priorité) le thread prêt de plus haute priorité. Le terme "Préemptif" veut dire que l'ordonnanceur use de son droit de préemption pour interrompre le thread courant de priorité moindre (le thread interrompu reste néanmoins prêt). Toutefois, il ne faut pas perdre de vu qu'un thread actif qui devient bloqué, ou qui termine rend la main à un autre thread, actif, même si celui-ci est de priorité moindre.
Il existe aussi des algorithmes qui permettent d'implémenter l'exclusion mutuelle à partir de quelques hypothèses de base et qui ne font aucunement appel aux priorités car un système de priorités ne peut en effet garantir des propriétés strictes comme l'exclusion mutuelle.
1.2. Moniteurs
Définition
On appellera Moniteur Java une classe dont: * toutes les variables sont privées (private); * toutes les méthodes sont synchronisées (synchronized).
Le respect de ces règles n'est pas une nécessité absolue. Mais il garantit un bon fonctionnement du programme. En RC (Région Critique), la variable partagée est globale. En Java, il n'existe pas de variables globales, et on doit passe la référence du moniteur à chaque thread contrôlée.
Si on analyse les diverses implantations du moniteur, on y trouve un socle commun garantissant une exclusion mutuelle d’accès à des variables partagées pendant une section critique de code; cette exclusion mutuelle est définie par «synchronized» en Java, par «protected» en Ada, par «monitor.Enter» et «monitor.Quit» en C#. Dans la terminologie des langages à objets, un objet partagé dont l’accès est contrôlé par un moniteur est accessible uniquement par des méthodes qui s’exécutent en exclusion mutuelle (encore que Java et C# autorisent aussi de ne mettre qu’un bloc d’instructions en exclusion mutuelle). Ce contrôle est assuré par un verrou d’accès à cet objet "monitoré".
Comme on l'a dit précédemment, chaque objet fournit un verrou, mais aussi un mécanisme de mise en attente (forme primitive de communication inter-threads) similaire aux variables de conditions ou aux moniteurs. On a vue que la méthode void wait() attend l'arrivée d'une condition sur l'objet sur lequel il s'applique (en général this). Il doit être appelé depuis l'intérieur d'une méthode ou d'un bloc "synchronized" (il y a aussi une version avec timeout) et que la méthode void notify() notifie un thread en attente d'une condition, de l'arrivée de celle-ci; cette méthode doit être appelée depuis une méthode déclarée "synchronized". Enfin, la méthode void notify_all() fait la même chose que "void notify()" mais pour tous les threads en attente sur l'objet.
1.3. Sémaphores
Un sémaphore binaire (il y a aussi d'autres sémaphores créés à partir des sémaphores binaires) est un objet sur lequel on peut appliquer les méthodes P() (un processus "acquiert" ou "verrouille" le sémaphore) et V() (un processus "relâche" ou "déverrouille" le sémaphore). Tant qu'un processus a un verrou sur le sémaphore, aucun autre ne peut l'obtenir.
On peut voir une implémentation dans un exercice.