Objet partagé
Comme on l'a vu au chapitre précédent, chaque thread possède son propre environnement, mais tous les objets créés par le programme se trouvent dans un unique tas. Différents threads vont donc pouvoir se partager un même objet. Il suffit en effet que ces derniers possèdent chacun une référence vers cet objet. Partons d'un exemple pour comprendre le principe d'objet partagé et voir les différents problèmes auxquels il faudra faire attention.
Classe BankAccount
On va définir une classe BankAccount qui représente un compte bancaire. Un tel compte est caractérisé par une somme se trouvant dessus (balance) qu'on peut connaitre en appelant la méthode getBalance. On peut également retirer de l'argent du compte avec la méthode withdraw. Voici le code complet de la classe :
|
|
BankAccount.La méthode withdraw permet donc de retirer de l'argent sur le compte, la quantité d'argent à retirer étant donnée en paramètre de la méthode. La première chose qu'on fait est de tester s'il reste assez d'argent sur le compte et si c'est le cas, on retire l'argent en décrémentant la variable d'instance balance.
Utilisation séquentielle
Pour bien comprendre où des problèmes peuvent apparaitre, voyons d'abord une utilisation de cette classe dans un programme ne comportant qu'un seul thread d'exécution. Supposons qu'Alice et Bob possèdent un compte bancaire partagé, auquel ils ont tous les deux accès complet. Chacun d'eux souhaite retirer 200 euros :
|
|
L'exécution du programme va bien entendu afficher sur la sortie standard :
Compte débité de 200 euros Il reste 0 euros sur le compte Il reste 0 euros sur le compte
Jusque là, pas de surprises, tout se déroule comme on l'a toujours vu.
Utilisation concurrente
Voyons maintenant ce qui se passe si on crée plusieurs threads qui vont accéder en parallèle à un même objet. On va donc créer deux threads, un pour Alice et un pour Bob. Ensuite, chacun de ces threads va tenter de retirer 200 euros sur le compte en même temps :
|
|
Pour bien comprendre un problème qui pourrait survenir, voyons tout d'abord, grâce à la figure suivante, ce qui est créé en mémoire une fois la méthode main complètement exécutée, aucun des deux threads créés n'ayant encore eu le temps de s'exécuter.

main du programme du listing 2.3.Comme on l'a vu au chapitre précédent, les threads ne peuvent pas toujours être exécutés réellement en parallèle et ils vont donc être ordonnancés par le scheduler. Pour certains de ces ordonnancements, un problème va survenir et on pourrait par exemple voir ceci sur la sortie standard :
Compte débité de 200 euros Il reste 0 euros sur le compte Compte débité de 200 euros Il reste -200 euros sur le compte
Mais que s'est-il passé ? Comment le solde du compte a-t-il pu devenir négatif malgré le test if (amount <= balance) qui est fait dans la méthode withdraw ? Ceci est dû à un ordonnancement particulier que nous allons de suite détailler. Voici un scénario d'exécution possible :
- Alice exécute la méthode
withdrawsur l'objet partagé, la condition duifest évaluée et vauttrue; - Le scheduler arrête d'exécuter Alice et passe à Bob ;
- Bob exécute la méthode
withdrawsur l'objet partagé, la condition duifest évaluée et vaut égalementtrue;
Ça y est ! Les deux threads sont entrés dans la méthode withdraw et ont tous les deux passés le test du if. Ils vont donc tous les deux pouvoir retirer 200 euros sur le compte. Lorsqu'on dispose d'un objet qui est utilisé par plusieurs threads à la fois, il faut donc être très prudent. La suite de ce chapitre va traiter précisément de ce problème, on va voir comment on peut pallier ce problème par le mécanisme de synchronisation.





















