US

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 :

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
public class BankAccount
{
    private int balance = 200;
 
    public int getBalance()
    {
        return balance;
    }
 
    public void withdraw (int amount)
    {
        if (amount <= balance)
        {
            balance -= amount;
            System.out.println ("Compte débité de " + amount + " euros");
        }
    }
}
Listing 2.1 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 :

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
public static void main (String[] args)
{
    // Le compte bancaire partagé
    BankAccount account = new BankAccount();
 
    // Alice veut retirer 200 euros
    account.withdraw (200);
    System.out.println ("Il reste " + account.getBalance() + " euros sur le compte");
 
    // Bob veut retirer 200 euros
    account.withdraw (200);
    System.out.println ("Il reste " + account.getBalance() + " euros sur le compte");
}
Listing 2.2 Utilisation séquentielle d'un objet.

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 :

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
 
12 
13 
14 
15 
16 
17 
18 
public static void main (String[] args)
{
    // Le compte bancaire partagé
    final BankAccount account = new BankAccount();
 
    class WithdrawMoney implements Runnable
    {
        public void run()
        {
            account.withdraw (200);
            System.out.println ("Il reste " + account.getBalance() + " euros sur le compte");
        }
    }
 
    // Alice et Bob veulent retirer 200 euros
    new Thread ("Alice", new WithdrawMoney());
    new Thread ("Bob", new WithdrawMoney());
}
Listing 2.3 Utilisation concurrente d'un objet.

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.

État de la mémoire après exécution de la méthode main du programme du listing 2.3
Figure 2.1 État de la mémoire après exécution de la méthode 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 :

  1. Alice exécute la méthode withdraw sur l'objet partagé, la condition du if est évaluée et vaut true ;
  2. Le scheduler arrête d'exécuter Alice et passe à Bob ;
  3. Bob exécute la méthode withdraw sur l'objet partagé, la condition du if est évaluée et vaut également true ;

Ç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.

  • Espace membre
  • Learning Center
  • Les forums
  • Livre d'or
  • Imprimer
  • Boutique
  • Info
  • Règlement
  • Erreur
  • Newsletter

MyPagerank.Net

Firefox 3.6

Browse Happy logo

Open Clip Art Library

Join our Facebook Group

Twitter

Copyright © 2000-2012 UKO. Toute reproduction strictement interdite sans autorisation du webmaster

Valid XHTML 1.1 !
Valid CSS2 !
Level Triple-A conformance icon, W3C-WAI Web Content Accessibility Guidelines 1.0
ICRA Internet Content Rating Association
Creative Commons License
Site optimisé pour Firefox avec une résolution 1024x768 --- Page chargée en 0.0439019 secondes --- This site uses Thumbshots previews