私の講師は、アカウント管理システムの更新にマルチスレッドを使用すると言っていました。以下はシステムの大まかな考え方です。
これが私のソースコードです。
アカウントクラス
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()を同期する必要があります。明確なアイデアを教えてください。
ご支援いただきありがとうございます。
アカウントクラス
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());
}}
他の答えが明確であったかどうかはわかりません。
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
を使用することで回避できることは、脚注の価値があります。ただし、高度化(当座貸越限度など)の追加を開始したらすぐに、同期に戻る必要があります。
設計を考慮して、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;
}
}
}
アカウントクラスはスレッドセーフではありません。 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);
}
障害シナリオを処理する必要がある場合があります。