Ces derniers temps j'ai passé pas mal d'entretiens en Java et je me suis rendu compte qu'il s'agissait toujours des même questions qui revenaient.
Voici donc une petite série sur les questions les plus classiques et comment y répondre.
Pour débuter nous allons parler de parallélisme et d'immutabilité un sujet très classique dans les entretiens.
Souvent le sujet sera introduit de la manière suivante:
Vous voulez paralléliser votre code, comment faites-vous ?
Réponse simple et efficace: en utilisant des threads. Et à partir de là ça se gâte en général !A quoi faut-il faire attention lors de l'utilisation des Threads ?
Le code utilisé avec des Threads doit être "Thread-Safe" afin d'éviter des incohérences dans le résultat du code.En particulier lors de l'écriture et de la lecture de valeurs il arrive bien souvent que l'on tombe sur des incohérences telles que:
- La valeur lue n'est pas la dernière valeur écrite en mémoire. Cela est du au fait que les threads disposent de leur propre cache et ne le synchronise pas forcément avec la mémoire centrale où est stockée la valeur.
- La valeur écrite par un Thread est écrasée par un autre.
Résultat: Le script ne se termine jamais car le thread ne voit pas que le champ keepRunning vient de passer à false
Et un autre pour le cas 2:
Résultat:
On voit que le thread 1 se lance et fait ses 10 boucles sans aucun problème.
A la fin de son exécution count vaut 10 ce qui est normal.
Malheureusement le thread 2 ne prend pas la suite car il a déjà démarré il y a quelques temps a récupéré la valeur de count qui valait alors 0 mais n'a pu faire son incrément qu'à la fin de l'exécution du thread 1.
Contrairement à ce qu'on pourrait croire l'opération "count++" n'est pas atomique, il s'agit d'une lecture puis d'une écriture dans le champ count.
Dans le cas présent le thread 2 a pu lire mais pas écrire la valeur dans count. Il ne le fait qu'à la toute fin de la boucle de t1. En fait il écrit sa valeur juste avant que t1 n'écrive "10" dans count.
Cela explique pourquoi t2 continue avec 11 au lieu de 2 s'il avait écrit après la fin de t1.
Dans tous les cas il n'a pu incrémenter correctement count et cela se voit sur le résultat final avec un count valant 19 au lieu de 20 si tout s'était déroulé normalement.
Comment éviter le problème de rafraîchissement de cache d'un Thread ?
Dans le cas présenté ci-dessus notre paramètre "keepRunning" est un paramètre servant à contrôler la fin d'une boucle. Typiquement dans ce cas le plus simple est d'utiliser le mot clé "synchronized".Pour faire simple ce mot clé permet d'indiquer à la JVM que le champ concerné sera accédé par différents threads et donc de stocker cette valeur dans la mémoire centrale et non dans le cache de chacun des threads.
Le cas d'utilisation typique est de mettre à jour un flag dans un thread et de le lire dans un autre:
Ce mot clé ne permet pas de résoudre le problème n°2 car il ne rend atomique que le get et le set et pas une combinaison des deux (ce qu'est le "count++").
Il n'est pas pertinent non plus d'utiliser le mot clé volatile dans certains cas:
- Le champ est déclaré "final" (voir la seconde partie sur l'immutabilité)
- Le champ n'est accédé que par un seul thread
- Le champ doit être protégé pour des accès complexes (comme dans notre exemple n°2).
Comment éviter le problème d'écrasement d'une valeur par un autre thread ?
La façon la plus simple d'éviter le problème n°2 est d'utiliser le mot clé "synchronized" qui permet de limiter l'exécution d'un bloc de code à un seul thread à la fois:
Le problème de l'utilisation du mot clé synchronized est qu'il induit un lock bloquant les autres threads ce qui est mauvais pour les performances et la scalabilité car il constitue un véritable goulet d'étranglement limitant le passage à un seul Thread à la fois.
Une autre solution est de n'échanger entre les threads que des objets immutables qui évitent les locks et permettent d'obtenir des résultats corrects.
Mais ça on le verra dans la seconde partie de l'article qui arrive bientôt ! (... :)
Post 12/52