Copy of https://perso.isima.fr/loic/java/exo_jse_executor.php
tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
Institut d'informatique ISIMA
  • twitter
  • linkedin
  • viadeo

[JavaSE] Threads et executors

 Cette page commence à dater. Son contenu n'est peut-être plus à jour. Contactez-moi si c'est le cas!

Date de première publication : 2015/09/29

Présentation de l'objectif

On veut modéliser l'implantation de colons sur une carte. Les colons vont se déplacer aléatoirement sur une carte jusqu'à trouver une ville et s'installer à côté de celle-ci. Les colons, quelle que soit leur origine, apparaissent sur les bords de la carte.

On vous propose de choisir une direction de marche (nord, sud, est, ouest) puis une distance maximale de marche. On répète le tout tant que le colon n'a pas trouvé la ville ou bien sur une borne max (le colon, épuisé, disparait)

Les déplacements des colons peuvent être complètement aléatoires, ou bien orientés (en direction de la ville originale).

Le résultat, l'implantation des colons, sera visualisé sur une interface Swing. Sauf à des fins de débogage, on ne demande pas la visualisation des déplacements des colons

Boite à outils - modélisation - rappels

Faisons une liste rapide des classes dont nous avons besoin :

Comme souvent, l'aléatoire va jouer un rôle important :

Random random = new Random();
int i = random.netxInt(100);

et on va essayer de faire du multithreading avec les classes Thread, Runnable, Executor et d'autres encores. Pour connaître, le nombre de processeurs disponibles, on a l'instruction suivante :

System.out.println("Available Processors: "+Runtime.getRuntime().availableProcessors());

et pour calculer les temps d'exécution des programmes, on pourra s'inspirer des lignes suivantes :

long debut = System.currentTimeMillis();
System.out.println("elapsed time: "+(System.currentTimeMillis()-debut));

Première version : Runnable avec ou sans Executor

Pour utiliser le multithreading, on va considérer que le déplacement globlal d'un colon est une tâche à réaliser. La classe Colon va donc implémenter l'interface Runnable et proposer la méthode run() qui va bien.

A la fin de la méthode run(), on n'oubliera pas de mettre à jour la carte et à rafraichir la zone d'affichage (il faudra donc paramétrer la classe ou les objets par ces éléments).

Pour exécuter les tâches ainsi définies, il suffit de faire quelque chose comme cela :

Colon colon = new Colon();
Thread t = new Thread(colon);
t.start();

Pour que cela soit plus "propre", nous allons utiliser le framework java.util.concurrent.Executor. Les tâches ne sont plus directement associées à un thread mais plutôt à une entité (l'exécutor) qui va ventiler les tâches sur des threads.

Par exemple, si la machine dispose de 4 processeurs, on peut créer un exécutor avec 4 threads d'exécution associés à une file commune de tâches.

ExecutorService es = Executors.newFixedThreadPool(4);
es.execute(colon);

Quand toutes les tâches ont été envoyées, on interdit à l'exécutor d'accepter de nouvelles tâches :

es.shutdown();

et on peut être notifié de la fin de toutes les tâches confiées, avec par exemple :

es.awaitTermination(2, TimeUnit.SECONDS);

Je vous propose de coder tout cela et de comparer les temps d'exécution en jouant avec le nombre de threads de l'exécutor.

La méthode getActiveCount() de l'exécutor permet de conaître le nombre de threads réellement utilisé

Version 2

Le problème c'est que la méthode run() proposée par l'interface Runnable ne permet pas de récupérer des informations, ce que permet la méthode call() de l'interface Callable.

Si on change le type de Colon, le code précédent marche en appelant la méthode submit() de l'exécutor au lien de execute().

Pour exploiter les "tâches" une fois qu'elles sont terminées, nous allons utiliser un service de complétion auquel on soumettre maintenant les "tâches" (méthode submit())

CompletionService<Colon2> cs = new ExecutorCompletionService<Colon>(es);

Le service de complétion permet grâce à la méthode take() de récupérer la tâche réalisée (placée dans une file). La tâche est de type Future, c'est-à-dire un résultat dont l'état peut être effectué, abandonné, pas encore fait ....

La méthode get() d'un objet Future est bloquante, jusqu'à ce que le résultat soit disponible.

Par exemple, cela m'a permis de sortir le rafraichissement de la classe Colon pour le placer dans le programme principal :

while (cs.take().get()!=null) repaint();

Aller plus loin ...

On peut faire des statistiques sur les colons : déplacement moyen, taux de réussite, ....

On peut aussi compléter l'interface graphique pour permettre de zoomer sur une carte relativement grande (C'est simple et direct avec les Graphics2D)

On pourrait afficher en temps réél les déplacements des colons