Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Conférence "Les secrets de la programmation concurrente"

Voici le compte-rendu de la conférence organisée par Zenika : "Les secrets de la programmation concurrente", animée par le Champion Java Heinz Kabutz.

Vous pouvez suivre la wave sur ce billet, ou directement depuis votre compte Wave à l'adresse suivante :
https://wave.google.com/wave/#restored:wave:googlewave.com!w%252Bgjndw-7qA

Note : pour les lecteurs ne possédant pas de compte Wave, une retranscription textuelle de la conférence est disponible en bas de ce billet.

Les secrets de la concurrence en Java, par Heinz Kabutz

Heinz Kabutz édite la newsletter The Java Specialists, qui comprend 50'000 lecteurs dans plus de 120 pays : javaspecialists.eu.

C'est également un Champion Java qui aime bien tenter des trucs tordus comme ajouter des enums au runtime... D'ailleurs à partir de lundi ce sera sûrement un Oracle Champion - ce qui veut surtout dire qu'il recevra des nouveaux Tshirts pour frimer aux couleurs d'Oracle :)

Il vit maintenant en Crète ("as long as you don't mess with their daughters, they're fine") et se déplace beaucoup pour enseigner le java, notamment en France où il est partenaire Zenika.

La conférence parle de la programmation concurrente.
Ecrire du code concurrent correct est un challenge : seul du code parfait fonctionnera - aucune place pour l'approximation, surtout que les erreurs ne se voient souvent qu'en production.

Voici 10 lois qui aident à appliquer des bonnes pratiques.

La loi de la sonnette sabotée

Si la sonnette excite le chien, qui réveille le gamin, qui énerve les parents, il est tentant de saboter directement la sonnette - problème résolu ! Ou pas.

Ne pas supprimer / ignorer les interruptions : il faut au contraire les gérer avec attention. Pas de catch (InterruptedException ex) { /* RIEN */ } !

Mais à quoi sert cette exception ? A envoyer un signal d'interruption à un autre thread, en appelant otherThread.interrupt(). C'est une façon plus "polie" de prévenir l'autre thread que l'ancienne méthode stop(), qui était brutale et incorrecte.

Quand un thread reçoit un signal d'interruption, son flag s'interruption est positionné à true. Si le thread est Waiting ou TimedWaiting, le thread lance un InterruptedException et éteint le flag. Sinon (si le thread est Running par exemple), le thread n'est pas prévenu et doit au contraire surveiller le flag via la méthode isInterrupted().

Comment gérer une InterruptedException ?

Option 1 : relancer la InterruptedException. C'est l'approche de l'api java.util.concurrent.
Option 2 : l'attraper et mettre manuellement le flag d'interruption à true, pour que le thread le consulte plus tard (à la fin d'une itération de boucle par exemple).

Ex:

  1. try {
  2. Thread.sleep(10);
  3. } catch (InterruptedException ex) {
  4. Thread.currentThread.interrupt();
  5. break;
  6. }

La loi de la photocopieuse Xerox

N'envoyez jamais les originaux de vos documents importants à l'administration : en cas de perte ou de problème, vous êtes fichus !
Morale : protégez vos données en copiant vos objets.

Les objets immutables sont naturellement thread-safe.

Comment utiliser un objet immuable ? Le code sera sûrement un peu différent, mais pas plus difficile à écrire ou maintenir - même moins dans le cas de code concurrent.

Avantages / inconvénients : produit beaucoup plus d'objets (davantage de GC), mais gestion de la concurrence beaucoup plus facile.

Heinz montre le code d'une liste immuable basée sur un tableau copié à chaque manipulation (insertion...).

Heinz râle contre les collections qui peuvent lancer des UnsupportedMethodException : le développeur n'a aucun moyen de savoir si la méthode est ou non réellement implémentée !

La loi de la foire-fouille bordélique

Avoir trop de threads dans une application est mauvais et dangereux. La performance va s'effondrer et le débuggage être impossible.

En fonction du profil de l'application et du serveur, il faut pouvoir répondre à:

  • nombre max de threads possibles actuellement ?
  • quel est le facteur limitatif ?
  • comment peut-on créer des threads supplémentaires ?

Heinz lance vi et écrit un classe Java en temps réel, pour tester les limites de sa machine (macbook évidemment).

  1. public static void main(String... args) {
  2. final Atomic=Integer threads = new AtomicInteger();
  3. while(true) {
  4. Thread t = new Thread() {
  5. public void run() {
  6. System.out.println(threads.incrementAndGet();
  7. try {
  8. Thread.sleep(100000000);
  9. } catch (InterruptedException iex) { return; }
  10. }
  11. };
  12. }
  13. }

Et boom, OutOfMemoryError à cause de la taille de la stack allouée à chaque thread (-Xss), au bout de ~ 2500 threads.

Pour créer des threads supplémentaires, une option est de réduire la taille de la stack par thread (ex: -Xss48k). Il est aussi conseillé d'utiliser des pools de threads.

Astuce : pour surveiller les threads : jstack pour dumper les process.

La loi de l'angle mort

Un thread peut ne pas voir les modifications locales effectuées par un autre, car la Java Memory model autorise la JVM à cacher les valeurs localement.

L'exemple classique :

  1. private boolean running = true;
  2. while(running) {
  3. ...
  4. }
  5. public void shutdown() {
  6. running = false;
  7. }

Il est possible que le thread ne voie jamais le changement de valeur dans le champ "running".

Solutions :

  • utiliser des champs "volatile" : cela interdit à la JVM de les mettre en cache.
  • utiliser des champs "final", leur valeur est fixée et est donc visible partout.
  • utiliser la synchronisation explicite (synchronized, Lock...).

La loi de la fuite d'information

Le compilateur est autorisé à réordonner le code à la volée. Cela peut provoquer des problèmes très subtils.

Comment interdire ou contrôler ce comportement ?
La JVM n'est pas autorisée à déplacer des instructions "externes" vers l'intérieur d'un bloc synchronized.

La loi du politicien corrompu

En absence d'un contrôle très strict, la corruption est inévitable. Il faut donc faire très attention à synchroniser correctement les opérations composites devant être atomiques.

L'exemple donné ici est celui de la simulation de transfert de fonds entre deux comptes.

  • Avant Java 5 : seul le mot-clé synchronized existait.
  • Depuis Java 5 : java.util.concurrent et les Locks, Barriers, Latches... Il est également possible d'utiliser des locks comportant un timeout. Mais attention, la syntaxe est un peu plus lourde, puisqu'il faut penser à délocker tous les locks explicites dans un bloc finally. ReadWriteLock permet de différencier les accès en lecture et en écriture, mais il faut faire très attention au risque de "starvation", même en mode "fair".

La loi du micromanagement

La performance ne "scale" pas bien.

Si aucun des suspects habituels (CPU, disque, réseau, GC...) n'est coupable de l'effondrement des perfs, il est très probable que ce soit dû à de la contention sur les locks.

Exemple de code stupide : locker sur une String ou un objet global (Boolean.TRUE...)

La loi de la conduite en Crète

"In Crete, don't actually stop at a stop sign if you treasure your car !"

En réalité, la JVM ne garantit pas l'application de toutes les règles. Un code apparemment correct est peut-être totalement faux quand même.

Les implémenteurs de JVMs font des paris parfois osés sur la façon dont votre code est écrit, dans l'espoir d'améliorer les perfs dans la majorité des cas. Mais il suffit que vous tombiez sur un cas tordu non prévu... Par exemple, les implémenteurs sont "encouragés" à ne pas "splitter" les variables sur 64 bits poru éviter le data shearing. Mais... ce n'est pas garanti.

Donc : apprenez les règles de la JVM quand même, pour détecter les possibles incohérences.

Heinz montre du code lançant deux threads tentant d'écrire de manière concurrente dans un long deux valeurs différentes : 0x11111... et 0xABCDEF...
Résultat : en lançant en mode 32bits, on constate (mais pas toujours) un data shearing : 0x11111ABCDEF

Mais ce n'est pas un bug à proprement parler : la JSR 133 l'autorise.

La loi des nouveaux riches

Ajouter des ressources (CPU, disque, meilleur réseau...) à un système apparemment stable peut le rendre instable.

Donnez 10000€ par mois à votre enfant, la première semaine il achètera une PS3, un home cinema... Au bout d'un mois il achètera plutôt de la drogue.

Exemple : passer d'un dual core à un dual core avec multithreading.

Les défauts qui apparaissaient une fois par mois auparavant peuvent maintenant survenir une fois par semaine.

De plus, un meilleur hardware peut accueillir davantage de clients, donc davantage d'opportunités de générer des problèmes de concurrence.

La loi du Lutefisk laissé dans l'assiette

"Lutefisk : it looks terrible, but the taste is even worse !"
Imaginez un Viking insistant pour que son enfant finisse son Lutefisk avant d'aller au lit : aucun des deux ne cèdera - deadlock.

C'est l'exemple du deadlock le plus classique : le thread 1 possède un lock sur A et attend B, et un thread 2 fait l'inverse.
Le problème, c'est qu'en Java un deadlock ne peut être résolu qu'en redémarrant la JVM.

Les deadlocks peuvent être détectés par la jconsole, mais il est impossible de les résoudre par ce biais.

Conclusion

La programmation concurrente est moins difficile quand on connaît bien les règles.

Voir :

  • les articles et la mailing-list sur javaspecialists.eu
  • la formation Java Expert chez Zenika.

Pour contacter Heinz : heinz@javaspecialists.eu

Questions / Réponses

Y a-t-il des outils spécialisés dans la concurrence ?
Certains chez google, d'autres sotn publics comme la console de Terracotta, mais Heinz doute de leur utilité. Quand le problème est là, c'est déjà trop tard.

Il existe aussi des langages déterministes dont on peut mathématiquement prouver qu'ils ne peuvent pas provoquer de deadlocks. Mais on ne peut rien faire de vraiment utile avec.


Commentaires

1. Le dimanche 14 février 2010, 11:52 par Thomas Queste

Vraiment bien vu ce principe de Wave.
J'ai pu relire mes notes en ayant les tiennes.

Beau boulot et belle exploit,

Tom

2. Le mardi 16 février 2010, 08:15 par Julien Dubois

Super ta wave! Désolé d'avoir manqué cet événement, et je suis très heureux d'avoir pu prendre un cours de rattrapage ici.

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.