web-dev-qa-db-ja.com

単純な現金預金引き出しプログラムでマルチスレッドを処理する方法

私の講師は、アカウント管理システムの更新にマルチスレッドを使用すると言っていました。以下はシステムの大まかな考え方です。 enter image description here

これが私のソースコードです。

アカウントクラス

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;
    }
}

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");
        }
    }
}

コードは正常に動作しています。しかし、私は何かがこのコードを欠いていると本当に思います。その欠陥を見つけるために私を助けていただけませんか。


ThreadExerciseクラスのmakeWithdraw()メソッドとdeposit()メソッドを同期するだけでは不十分であり、その同期を削除して、Accountクラスのwithdraw()とdeposit()を同期する必要があります。明確なアイデアを教えてください。
ご支援いただきありがとうございます。

9
Pippi

アカウントクラス

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();
    }
}}

人のクラス

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;
}}

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());
}}
4
Isuru Srimal

他の答えが明確であったかどうかはわかりません。

synchronizedクラスのメソッドをThreadExerciseしました。つまり、特定のThreadExerciseオブジェクトでこれらのメソッドを一度に呼び出すことができるスレッドは1つだけです。とにかく、各スレッドオブジェクトはそのような1つのオブジェクトのメソッドのみを呼び出すため、影響はありません。

synchronizeクラスのメソッドをAccountして、特定のAccountで一度に1つのスレッドのみが1つのメソッドを呼び出すようにする必要があります。

もちろん、実際のシステムでは、Accountオブジェクトは(なんらかの理由で)データベースまたはオブジェクトストアにシリアル化され、アプリケーションが1つの「物理」を表す2つの「doppelganger」オブジェクトを導入しないようにする必要があります。アカウント。複数のATMスイッチを備えた分散システムでは、これは注意が必要です。

バランス転送のアイデアを導入する場合、さらに同期を導入する必要があるかもしれません。これは、一部のオブザーバープロセスが見ることが受け入れられない場合に特に当てはまります。

Account 1: $100
Account 2: $0

Account 1: $40
Account 2: $0

Account 1: $40
Account 2: $60

$ 60の送金が消えて再び現れるように見えます。そこにビジネス上の問題はありません。銀行は何百万人もの人たちにXからお金を受け取り、それをYに渡します。メソッドにsynchronizedを追加するだけでは、同時プログラミングに対する完全な答えではないことを指摘します。

私はかつて、そのような転送を実行し、途中でエラーが発生し、アカウントを状態のままにしていた組織を見たことがあります。

Account 1: $100
Account 2: $60

アカウント1から2までの$ 60($ 1千万IRL)が到着したが、そこから出たことはない!それは別の話です...

ただし、質問に答えるには:

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;
    }
}

私は挑発的に同期されていませんgetBal()。一方でintの読み取りと書き込みはアトミックであるため、常にbalanceの一貫した値が読み取られ、書き込み操作によって下位バイトのみが更新された「粗悪な」ものは読み取られません。これをlongに変更した場合、JVMはそのことを保証しなくなります。

ただし、同期しないと、異常な位置にあることがわかります。

  Account 1: $100
  Account 2: $60

コードが次の場合でも発生する可能性があります。

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

これは、同期によってブロッキングが発生するだけでなく、メモリのバリアにも影響を与えるためです。別のスレッドがaccount1をキャッシュしていたが、同期せずにaccount2をキャッシュしていなかったとすると、account1が古くなっていることはわかりませんが、account2の最新バージョンをフェッチします。

クラスが非常に単純であり、addAndGet(int delta)を使用してJava.util.concurrent.atomic.AtomicIntegerを使用することで回避できることは、脚注の価値があります。ただし、高度化(当座貸越限度など)の追加を開始したらすぐに、同期に戻る必要があります。

2
Persixty

設計を考慮して、Accountクラスを同期する必要があります(その中のメソッド)。

現在、他の誰かがインスタンスをアカウントに取得して、スレッドセーフではない方法で使用する可能性があります。この場合、単に他の場所からAccountのメソッドを呼び出すと、それがうまくいきません。

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

アカウントクラスはスレッドセーフではありません。 ThreadExerciseクラスのDeposit&withdrawメソッドを同期しましたが、deposit/withdrawがスレッドをロックしている間に基になるバランスを変更できます。

シナリオを検討

スレッド1はThreadExercise.depositを呼び出し、残高をチェックして待機します。同時に、スレッド2がウェイクアップしてバランスを更新します。

そのため、あなたの口座残高は実際には同時預金+引き出し呼び出しに対して同期されていません。

バランスは以下のように定義できます。

 AtomicInteger balance = new AtomicInteger(1000);

次に、withdrawメソッドは次のように書くことができます

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);
}

障害シナリオを処理する必要がある場合があります。

2
Himanshu Ahire