[英]synchronize concept in java doesn't work?
我們在銀行有 100 個賬戶和兩個作為線程實現的職員,他們使用同步方法 transferMoney 從編號為 accountNumberFrom 的賬戶向賬戶 accountNumberTo 轉移 1000 倍的資金。 由於所有帳戶都以余額 0 開始,並且從一個帳戶中提取的資金會轉移到另一個帳戶,因此在所有交易后余額應為零。 大多數情況下都是如此,但並非總是如此。 雖然很少出現,但有時交易后的余額不等於0。這是怎么回事?
public class Clerk extends Thread {
private Bank bank;
public Clerk(String name, Bank bank) {
super(name);
this.bank=bank;
start();
}
public void run() {
for (long i=0; i<1000; i++) {
int accountNumberFrom = (int) (Math.random()*100);
int accountNumberTo = (int) (Math.random()*100);
float amount = (int) (Math.random()*1000) - 500;
bank.transferMoney(accountNumberFrom, amount);
bank.transferMoney(accountNumberTo, -amount);
}
}
}
and a class Bank
public class Bank {
Account[] account;
public Bank() {
account = new Account[100];
for (int i=0; i < account.length; i++)
account[i] = new Account();
}
public synchronized void transferMoney(int accountNumber, float amount) {
float oldBalance = account[accountNumber].getBalance();
float newBalance = oldBalance + amount;
account[accountNumber].setBalance(newBalance);
}
}
public class Banking {
public static void main (String[] args) {
Bank myBank = new Bank();
/**
* balance before transactions
*/
float sum=0;
for (int i=0; i<100; i++)
sum+=myBank.account[i].getBalance();
System.out.println("before: " + sum);
new Clerk ("Tom", myBank);
new Clerk ("Dick", myBank);
/**
* balance after transactions
*/
for (int i=0; i<100; i++)
sum+=myBank.account[i].getBalance();
System.out.println("after: " + sum);
}
}
一個問題是同步transferMoney
方法只使用一個帳戶,因此在將轉賬金額添加到“to”帳戶之后但在從“from”帳戶中扣除之前,另一個線程可能可以訪問帳戶余額。 如果所有帳戶都從零開始,我們可以有以下事件序列:
在第 2 步,我們看到所有賬戶的總額是 100 美元而不是零。
因此, transferMoney
方法在持有同步鎖的同時更新兩個帳戶很重要。
另一個問題是,雖然transferMoney
是同步的,但匯總帳戶余額的代碼(上面的第 2 步)卻不是。 所以即使你在transferMoney
方法中更新了兩個賬戶,上面的事件序列仍然會發生,因為主線程在執行第 2 步之前沒有同步。
我會將匯總帳戶的代碼移動到Bank
並使其同步。 這將使兩個方法在Bank
實例上同步並防止出現錯誤錯誤的事件序列。
次要問題是,在主線程中,您無需等待職員完成轉賬。 您的代碼正在執行所有 1,000 次轉賬,但您只需在啟動事務員線程后立即檢查余額,因此您可能會在 0 次轉賬后查看余額,或者在所有 1,000 次轉賬之后,或在 639 次轉賬之后查看余額,誰知道呢。 正確執行同步將防止您看到非零的總余額,但您仍應等待職員完成。 (試一試,如果你不明白,請發布一個新問題。)
在您的示例中, synchronized
只阻止了對myBank.transferMoney
所有線程調用,但並不能確保每個線程都在main thread
線程上完成,您可以像這樣更新源代碼:
class Clerk extends Thread {
private Bank bank;
private volatile boolean done;
public Clerk(String name, Bank bank) {
super(name);
this.done = false;
this.bank=bank;
start();
}
public void run() {
for (long i=0; i<1000; i++) {
int accountNumberFrom = (int) (Math.random()*100);
int accountNumberTo = (int) (Math.random()*100);
float amount = (int) (Math.random()*1000) - 500;
bank.transferMoney(accountNumberFrom, amount);
bank.transferMoney(accountNumberTo, -amount);
}
this.done = true;
}
public boolean isDone() {
return done;
}
}
class Account {
protected float balance;
public float getBalance() {
return balance;
}
public void setBalance(float newBalance) {
this.balance = newBalance;
}
}
class Bank {
Account[] account;
public Bank() {
account = new Account[100];
for (int i=0; i < account.length; i++)
account[i] = new Account();
}
public synchronized void transferMoney(int accountNumber, float amount) {
float oldBalance = account[accountNumber].getBalance();
float newBalance = oldBalance + amount;
account[accountNumber].setBalance(newBalance);
}
}
public class Banking {
public static void main (String[] args) throws Exception {
for(int j = 0 ; j < 1000 ; ++j) {
Bank myBank = new Bank();
/**
* balance before transactions
*/
float sum=0;
for (int i=0; i<100; i++)
sum+=myBank.account[i].getBalance();
System.out.println("before: " + sum);
Clerk a = new Clerk ("Tom", myBank);
Clerk b = new Clerk ("Dick", myBank);
while(!a.isDone() || !b.isDone()) // wait util all thread done
Thread.sleep(1);
/**
* balance after transactions
*/
for (int i=0; i<100; i++)
sum+=myBank.account[i].getBalance();
System.out.println("after: " + sum);
}
}
}
非常感謝有用的答案。 我修改了我的代碼,現在它可以正常工作:
public class Bank
{
Account[] account;
public Bank() {
account = new Account[100];
for (int i=0; i < account.length; i++)
account[i] = new Account();
}
public void transferMoney(int accountNumber, float amount) {
synchronized (account[accountNumber]) {
float oldBalance = account[accountNumber].getBalance();
float newBalance = oldBalance - amount;
account[accountNumber].setBalance(newBalance);
}
}
}
public class Account {
private float balance;
public void setBalance(float balance) {
this.balance=balance;
}
public float getBalance() {
return this.balance;
}
}
public class Clerk extends Thread {
private Bank bank;
public Clerk(String name, Bank bank) {
super(name);
this.bank=bank;
}
public void run() {
for (long i=0; i<100; i++) {
int accountNumberFrom = (int) (Math.random()*100);
int accountNumberTo = (int) (Math.random()*100);
float amount = (int) (Math.random()*1000);
bank.transferMoney(accountNumberFrom, -amount);
bank.transferMoney(accountNumberTo, amount);
}
}
}
public class Accountant extends Thread
{
Bank bank;
public Accountant(String name, Bank bank)
{
super(name);
this.bank=bank;
}
@Override public void run() {
getBalance();
}
public synchronized void getBalance() {
float sum=0;
System.out.println(Thread.currentThread().getName());
for (int i=0; i<100; i++)
sum+=bank.account[i].getBalance();
System.out.println("Bilanz: " + sum);
}
}
public class Banking {
public Banking() {
}
public static void main(String[] args) {
Bank myBank = new Bank();
Clerk tom = new Clerk ("Tom", myBank);
Clerk dick = new Clerk ("Dick", myBank);
Accountant harry = new Accountant("Harry", myBank);
tom.start();
dick.start();
try {
System.out.println("Current Thread: " + Thread.currentThread().getName());
tom.join();
dick.join();
}
catch(Exception x) {
System.out.println("Exception has " + "been caught" + x);
}
harry.start();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.