我们在银行中有一百个帐户,有两个作为线程实现的职员,他们使用同步方法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);
}
}
答案 0 :(得分:1)
一个问题是,同步的transferMoney
方法仅使用一个帐户,因此在转移金额已添加到“至”帐户之后,另一个线程可能会在 中访问帐户余额但之前已从“发件人”帐户中扣除。如果所有帐户都从零开始,那么我们可能会发生以下一系列事件:
在第2步中,我们看到所有帐户的总金额为100美元,而不是零。
因此,transferMoney
方法在保持同步锁的同时更新两个帐户非常重要。
另一个问题是,在transferMoney
同步期间,总计帐户余额的代码(上面的步骤2)却没有。因此,即使您同时使用transferMoney
方法更新两个帐户,上述事件的顺序仍然可能发生,因为在执行步骤2之前主线程不会同步。
我会将总计帐户的代码移至Bank
并使其也同步。这将使两种方法在Bank
实例上同步,并防止产生错误错误的事件序列。
第二个问题是在主线程中,您不必等待文员完成其转移。您的代码正在执行所有1,000次传输,但是您只是在启动店员线程后立即检查余额,因此,您可能会在0次传输之后,在所有1,000次传输之后或在639次传输之后查看余额(知道)。正确执行同步将防止您看到总余额不为零,但是您仍应等待文员完成。 (尝试一下,如果您无法解决,请提出一个新问题。)
答案 1 :(得分:0)
在您的示例中,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);
}
}
}
答案 2 :(得分:0)
非常感谢您提供有用的答案。我修改了代码,现在它可以正常工作了:
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();
}
}