J2SE Threads, 1ère partie Principe Cycle de vie Création Synchronisation Cycle Ingénierie 2e année SIGL Dernière mise à jour : 19/10/2006 Christophe Porteneuve
Threads Principes Cycle de vie Création java.lang.thread java.lang.runnable Synchronisation 2/22
Threads Principes Un thread est comme un programme dans le programme On peut avoir plusieurs threads pour un seul programme Les threads s exécutent «en parallèle» Les threads et les données Un champ peut être «partagé» par plusieurs threads, ou Chaque thread peut avoir sa propre «version» du champ Les threads et le code Il est parfois nécessaire d empêcher que certaines méthodes soient exécutées simultanément par différents threads, pour éviter des résultats incohérents L exécution en parallèle peut poser certains problèmes, ex. attente mutuelle (deadlocks). D où plus de réflexion en amont 3/22
Threads Cycle de vie Un thread a le cycle de vie suivant : Nouveau thread start En cours yield Runnable sleep / wait / blocage E/S fin de sleep / notify / interrupt Vivant (isalive() == true) Non Runnable / Suspendu Mort la méthode run() se termine 4/22
Threads Création Il existe deux manières de créer des threads Extension de java.lang.thread Utile si on va créer plusieurs threads similaires, éventuellement paramétrés différemment Utile si on veut enrichir les capacités des objets threads avec des méthodes publiques supplémentaires, etc. Implémentation de java.lang.runnable Pratique pour des classes thread anonymes, donc des implémentations ponctuelles et simples Pratique aussi pour ajouter la capacité à être threadé sur une classe existante 5/22
Threads java.lang.thread (1/2) La classe java.lang.thread contient notamment : De nombreux constructeurs, mais essentiellement : Un vide (classe contenant le code à exécuter, threads anonymes) Un prenant un argument de nom pour différenciers les threads Un prenant un objet auquel déléguer le code à exécuter Un prenant deux arguments : le nom et un objet délégué currentthread(), statique, pour connaître le thread courant Des méthodes de gestion du cycle de vie (ex. isalive()) Des méthodes de synchronisation spécifiques (ex. join()) 6/22
Threads java.lang.thread (2/2) public class MyThread extends Thread { public MyThread(final String name) { super(name); public void run() { // le code effectif est ici public class MyApp { public static void main(final String[] args) { new MyThread("Toto").start(); 7/22
Threads java.lang.runnable (1/2) L interface java.lang.runnable a une seule méthode : public interface Runnable { public void run(); Cette méthode contient simplement le code a exécuter Un objet implémentant Runnable est passé en argument à un objet Thread à la construction code délégué 8/22
Threads java.lang.runnable (2/2) public class RunnablePrimeCruncher extends PrimeCruncher implements Runnable { public void run() { crunchprime(); Ici, on suppose qu il existait une classe de calculs sur nombres premiers, PrimeCruncher, dont la méthode de travail serait crunchprime(). Pour pouvoir lancer cela dans un thread, on en fait une variante implémentant Runnable et donc run(). public class MyApp { public static void main(final String[] args) { final RunnablePrimeCruncher cruncher = new RunnablePrimeCruncher(1000); new MyThread("Cronch", cruncher).start(); 9/22
Threads Endormissement, pause légère Un thread peut décider de «s endormir» un moment. Pendant ce temps, il est toujours considéré en vie (alive) mais pas running : dans son cycle de vie, il est not runnable. On utilise sleep(long millis) Un thread peut aussi décider de «laisser le passage» à d autres threads, (très) temporairement. Pendant ce temps, il est néanmoins considéré running. On utilise yield() La JVM décide du temps de pause en fonction des charges et priorités respectives des autres threads actifs. Evitez de l utilisez à tout bout de champ : cela affecterait négativement les performances. 10/22
Threads Synchronisation (1/8) Lorsque plusieurs threads s exécutent en parallèle, un thread risque de vouloir accéder (en lecture ou écriture) à une donnée alors qu un autre thread y accède en écriture vouloir exécuter une méthode «à exécution unique» (par exemple, qui utilise une ressource exclusive, telle qu un flux sur fichier/socket) alors qu un autre thread est déjà en train de l exécuter Si plusieurs threads collaborent pour résoudre un problème qui a été divisé en sous-parties, ils ont souvent besoin de s attendre Ex. tri réparti (tri rapide, poids équivalents en tri topologique ) 11/22
Threads Synchronisation (2/8) On peut synchroniser l exécution de plusieurs threads En demandant à un thread A d attendre une notification d un thread B En lui demandant d attendre la terminaison d un thread B En protégeant une ressource/méthode contre des accès simultanés Moniteurs Chaque objet Java dispose en interne d un «moniteur», un verrou pour l exclusion mutuelle : tant qu un thread le détient, tout autre tentant de l obtenir va bloquer jusqu à la libération. Passer en attente (wait()) libère atomiquement le verrou Se faire notifier ou interrompre récupère atomiquement le verrou, si on l avait avant mise en attente ou E/S interruptible. 12/22
Threads Synchronisation (3/8) Exemple de «race condition» sur un accès à un champ : public class ParallelSorter implements Runnable { private static int _swapcount = 0; public void run() { // Algorithme de tri, et dedans : _swapcount++; // // run Imaginons un tri rapide qui lance de nouveaux threads pour chaque tri récursif sur les deux partitions Rien ne dit que n threads ne vont pas tenter de modifier _swapcount au même moment. Voyons un schéma 13/22
Threads Synchronisation (4/8) _swapcount T1 T2 T3 T4 T5 T6 T7 0 1 2 Résultat correct : 14 3 4 5 6 7 8 9 10 11 14/22
Threads Synchronisation (5/8) Comment synchroniser l accès simplement : public class ParallelSorter implements Runnable { private static final Object _scmonitor = new Object(); private static int _swapcount = 0; public void run() { // Algorithme de tri, et dedans : synchronized (_scmonitor) { _swapcount++; // // run Il s agit d un bloc synchronisé, qui repose sur le verrou de la référence privée _scmonitor. 15/22
Threads Synchronisation (6/8) _swapcount T1 T2 T3 T4 T5 T6 T7 0 1 2 Résultat correct! 3 4 5 6 7,8 9 10 11, 12 13, 14 attend 16/22
Threads Synchronisation (7/8) On peut aussi «verrouiller» toute une méthode : public final class BankAccount { public synchronized void withdraw(final double amount) { _balance -= amount; // withdraw Dans un tel cas, c est le verrou de l objet conteneur qui est utilisé. Cette approche est simple mais parfois vulnérable : Un «code malicieux» pourrait acquérir dans un thread concurrent le moniteur de votre objet et ne jamais le relâcher, empêchant vos méthodes synchronisées de s exécuter. 17/22
Threads Synchronisation (8/8) Quelques détails supplémentaires sur les moniteurs Dans la terminologie Java, «verrou» (lock) et «moniteur» (monitor) sont interchangeables. Un verrou est acquis par le thread, ce qui veut dire qu au sein d un même thread, une chaîne d appels (call stack) contenant plusieurs méthodes/blocs synchronisés d après le même verrou ne bloquera pas. 18/22