web-dev-qa-db-fra.com

Comment gérer le multithreading dans un simple programme de retrait de dépôt en espèces

Mon instructeur a dit d'utiliser le multi-threading pour mettre à jour un système de gestion de compte. Ci-dessous est une idée approximative du système .enter image description here

Voici mon code source pour cela. 

Classe de compte

public class Account {
    int balance= 1000;

    public int getBal(){
        return balance;
    }

    public void withdraw(int bal){
        balance= balance-bal;
    }

    public void deposit(int bal){
        balance= balance+bal;
    }
}

Classe ThreadExercise

public class ThreadExercise implements Runnable{

    Account acc = new Account();

    public static void main(String[] args) {
        ThreadExercise ts = new ThreadExercise();
        Thread t1 = new Thread(ts, "person 1");
        Thread t2 = new Thread(ts, "person 2");
        Thread t3 = new Thread(ts, "person 3");
        t1.start();
        t2.start();
        t3.start();
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            makeWithdraw(100);
            if (acc.getBal() < 0) {
                System.out.println("account is overdrawn!");
            }
            deposit(200);
        }
    }


    private synchronized void makeWithdraw(int bal){
        if (acc.getBal()>=bal) {
            System.out.println(Thread.currentThread().getName()+" "+ "is try to withdraw");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            acc.withdraw(bal);
            System.out.println(Thread.currentThread().getName()+" "+ "is complete the withdraw");
        }else{        
            System.out.println(Thread.currentThread().getName()+ " "+"doesn't have enough money for withdraw ");
        }
    }

    private synchronized void deposit(int bal){
        if (bal>0) {
            System.out.println(Thread.currentThread().getName()+" "+ " is try to deposit");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            acc.deposit(bal);
            System.out.println(Thread.currentThread().getName()+" "+ "is complete the deposit");
        }else{        
            System.out.println(Thread.currentThread().getName()+ " "+"doesn't have enough money for deposit");
        }
    }
}

Le code fonctionne bien. Mais je pense vraiment que quelque chose manque à ce code. Pouvez-vous m'aider s'il vous plaît pour trouver cette faute. 


Ne suffit-il pas de synchroniser les méthodes makeWithdraw () et deposit () dans la classe ThreadExercise et dois-je supprimer cette synchronisation et synchroniser les méthodes retire () et deposit () dans la classe Account S'il vous plaît donnez-moi une idée claire.
Merci pour votre soutien. 

7
pippi longstocking

Classe de compte

public class Account {
public static Account account;
private static int balance = 1000;
private static Person person;

private Account() {
}

public static Account getAccount(Person p) {
    if (account == null) {
        account = new Account();
    }
    Account.person = p;
    return account;
}

public static int getBal() {
    return balance;
}

public synchronized void withdraw(int bal) {
    try {

        if (balance >= bal) {
            System.out.println(person.getName() + " " + "is try to withdraw");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            balance = balance - bal;
            System.out.println(person.getName() + " " + "is complete the withdraw");
        } else {
            System.out.println(person.getName() + " " + "doesn't have enough money for withdraw ");
        }
        System.out.println(person.getName() + " " + " withdraw Rs." + balance);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public synchronized void deposit(int bal) {
    try {
        if (bal > 0) {
            System.out.println(person.getName() + " " + " is try to deposit");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            balance = balance + bal;
            System.out.println(person.getName() + " " + "is complete the deposit");
        } else {
            System.out.println(person.getName() + " " + "doesn't have enough money for deposit");
        }
        System.out.println(person.getName() + " " + " deposit Rs." + balance);
    } catch (Exception e) {
        e.printStackTrace();
    }
}}

Classe de personne

public class Person {
private String name;

public Person(String name) {
    this.name = name;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

@Override
public String toString() {
    return name;
}}

Classe ThreadExercise

public class ThreadExercise extends Thread implements Runnable {

private Person person;

public ThreadExercise(Person p) {
    this.person = p;
}

public static void main(String[] args) {

    ThreadExercise ts1 = new ThreadExercise(new Person("person 1"));
    ts1.start();
    ThreadExercise ts2 = new ThreadExercise(new Person("person 2"));
    ts2.start();
    ThreadExercise ts3 = new ThreadExercise(new Person("person 3"));
    ts3.start();

}

@Override
public void run() {
    for (int i = 0; i < 3; i++) {
        try {
            Account acc = Account.getAccount(person);
            acc.withdraw(100);
            try {
                Thread.sleep(200);
            } catch (InterruptedException ex) {
                Logger.getLogger(ThreadExercise.class.getName()).log(Level.SEVERE, null, ex);
            }
            if (acc.getBal() < 0) {
                System.out.println("account is overdrawn!");
            }
            acc.deposit(200);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    System.out.println("Final Acc balance is Rs." + Account.getBal());
}}
3
Isuru Srimal

Je ne suis pas sûr que les autres réponses soient claires.

Vous avez synchronized les méthodes de la classe ThreadExercise . Cela signifie qu'un seul thread peut appeler ces méthodes sur un objet ThreadExercise donné à la fois . Cela n'a aucun effet, car chaque objet thread n'invoquera que des méthodes sur un tel objet. en tous cas.

Vous devez synchronize les méthodes de la classe Account pour vous assurer qu'un seul thread appelle une méthode sur un Account donné à la fois.

Bien sûr, dans tout système réel, les objets Account seraient (en quelque sorte) sérialisés dans une base de données ou un magasin d'objets et vous deviez vous assurer que votre application n'introduisait pas deux objets "doppelganger" qui représentent un compte "physique". Cela peut être délicat sur un système distribué avec plusieurs commutateurs ATM.

Si vous introduisez l'idée d'un transfert de solde, vous devrez peut-être introduire une synchronisation plus poussée. C'est particulièrement vrai s'il était inacceptable pour un processus d'observateur de voir:

Account 1: $100
Account 2: $0

Account 1: $40
Account 2: $0

Account 1: $40
Account 2: $60

Dans lequel un transfert de 60 $ est vu disparaître et réapparaître ... Il n'y a pas de problème commercial ici. Les banques gagnent des millions en prenant l'argent sur X, puis en le cédant à Y sans aucune raison valable de pouvoir traire leurs clients ... Je ne fais que souligner que l'ajout de synchronized aux méthodes n'est pas la seule réponse à programmation simultanée.

Une fois, j'ai vu une organisation qui avait réussi à exécuter un tel transfert, avait une erreur au milieu et laissait les comptes dans l'état:

Account 1: $100
Account 2: $60

Où 60 $ (10 millions de dollars IRL) du compte 1 à 2 sont arrivés mais ne sont jamais partis! C'est une autre histoire ...

Cependant pour répondre à la question:

public class Account {
    int balance= 1000;

    public int getBal(){
        return balance;
    }

    public synchronized void withdraw(int bal){
        balance= balance-bal;
    }

    public synchronized void deposit(int bal){
        balance= balance+bal;
    }
}

Je n'ai pas synchronisé getBal() de manière provocante. D'une part, les lectures et écritures int sont atomiques, de sorte qu'elles liront toujours une valeur cohérente de balance et non un «bâtard» où (par exemple) une opération d'écriture n'a mis à jour que les octets de poids faible. Si vous l'avez changé en long, la JVM ne fait plus cette garantie.

Cependant, ne pas synchroniser cela signifie que vous pouvez voir cette position anormale:

  Account 1: $100
  Account 2: $60

Cela pourrait se produire même si votre code était:

account1.withdraw(60);
account2.deposit(60);

En effet, la synchronisation n'introduit pas seulement le blocage, mais crée également une barrière de mémoire. Supposons qu'un thread séparé ait account1 mis en cache, mais pas account2 sans synchronisation. Il ne saurait pas que account1 est obsolète, mais récupérez une version à jour de account2.

Cela vaut la peine de noter que votre classe est si simple que vous pourriez utiliser Java.util.concurrent.atomic.AtomicInteger à l'aide de addAndGet(int delta). Toutefois, dès que vous commencez à ajouter une sophistication (telle qu'une limite de découvert), vous devrez revenir à la synchronisation.

2
Persixty

Compte tenu de la conception, la classe Account doit être synchronisée (les méthodes qu’elle contient).

La manière dont il est actuellement utilisé par quelqu'un d'autre peut extraire une instance sur un compte et l'utiliser de manière non thread-safe. Dans ce cas, le simple fait d’invoquer les méthodes de gestion de compte à un autre endroit risquerait de le faire croire.

public class Account {
  private final Object lock = new Object();
  // Must be private to be thread-safe!
  private int balance= 1000;

  public int getBal(){
    return balance;
  }

  public synchronized void withdraw(int bal){
    synchronized (lock) {
      balance= balance-bal;
    }
  }

  public synchronized void deposit(int bal){
    synchronized (lock) {
      balance= balance+bal;
    }
  }
}
2
Sebastian

Votre classe de compte n'est pas thread-safe. Bien que vous ayez synchronisé les méthodes de dépôt et de retrait de la classe ThreadExercise, le solde sous-jacent peut être modifié tant que le dépôt/retrait a verrouillé le thread. 

Considérez un scénario

Thread 1 appelle ThreadExercise.deposit, il vérifie la balance et attend .. .. La même heure, Thread 2 se réveille et met à jour la balance. 

Votre solde de compte n'est donc pas vraiment synchronisé avec les appels de dépôt et de retrait simultanés.

Vous pouvez définir le solde comme ci-dessous.

 AtomicInteger balance = new AtomicInteger(1000);

Ensuite, la méthode de retrait peut être écrite comme ci-dessous

public boolean withdraw (int amtToWithdraw, int existingBalance){
    return balance.compareAndSet(existingBalance,existingBalance-amtToWithdraw); 
}

public void deposit(int amtToDeposit, int existingBalance){
    return balance.compareAndSet(existingBalance,existingBalance+amtToDeposit);
}

Vous devrez peut-être gérer le scénario d'échec.

2
Himanshu Ahire