简体   繁体   English

java中的同步概念不起作用?

[英]synchronize concept in java doesn't work?

We have one hundred accounts in the bank and two clerks, implemented as threads, who transfer each 1000 times money from account with number accountNumberFrom to account accountNumberTo, using the synchronized method transferMoney.我们在银行有 100 个账户和两个作为线程实现的职员,他们使用同步方法 transferMoney 从编号为 accountNumberFrom 的账户向账户 accountNumberTo 转移 1000 倍的资金。 Since all accounts start with balance 0 and the money retrieved from one account is transferred to another, the balance should be zero after all the transactions.由于所有帐户都以余额 0 开始,并且从一个帐户中提取的资金会转移到另一个帐户,因此在所有交易后余额应为零。 This is true most of the time, but not always.大多数情况下都是如此,但并非总是如此。 Although it occurs seldom, but sometimes the balance after the transactions is not equal to 0. What is wrong?虽然很少出现,但有时交易后的余额不等于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);
    }
}

One issue is that the synchronized transferMoney method only takes one account and so it's possible another thread could access the account balances after the transfer amount has been added to the "to" account but before it has been deducted from the "from" account.一个问题是同步transferMoney方法只使用一个帐户,因此将转账金额添加到“to”帐户之后但从“from”帐户中扣除之前,另一个线程可能可以访问帐户余额。 If all accounts start at zero, we could have this sequence of events:如果所有帐户都从零开始,我们可以有以下事件序列:

  1. Clerk Tom adds $100 to account 1.书记员汤姆向帐户 1 添加了 100 美元。
  2. Main thread totals account balances.主线程总计帐户余额。
  3. Clerk Tom deducts $100 from account 2.书记员汤姆从帐户 2 中扣除了 100 美元。

At step 2 we see that the total of all accounts is $100 instead of zero.在第 2 步,我们看到所有账户的总额是 100 美元而不是零。

So it's important that the transferMoney method update both accounts while holding the synchronized lock.因此, transferMoney方法在持有同步锁的同时更新两个帐户很重要。

Another issue is that while the transferMoney is synchronized, the code that totals the account balances (step 2 above) is not.另一个问题是,虽然transferMoney是同步的,但汇总帐户余额的代码(上面的第 2 步)却不是。 So even if you update both accounts in the transferMoney method, the sequence of events above can still happen because the main thread doesn't synchronize before executing step 2.所以即使你在transferMoney方法中更新了两个账户,上面的事件序列仍然会发生,因为主线程在执行第 2 步之前没有同步。

I would move the code that totals up the accounts to Bank and make it synchronized too.我会将汇总帐户的代码移动到Bank并使其同步。 That will make both methods synchronized on the Bank instance and prevent the sequence of events that gives the wrong error.这将使两个方法在Bank实例上同步并防止出现错误错误的事件序列。

A secondary issue is that in the main thread, you don't wait for the clerks to finish their transfers.次要问题是,在主线程中,您无需等待职员完成转账。 Your code is performing all 1,000 transfers, but you just check the balances right after you start the clerk threads, so you may be looking at the balances after 0 transfers, or after all 1,000, or after 639 transfers, who knows.您的代码正在执行所有 1,000 次转账,但您只需在启动事务员线程后立即检查余额,因此您可能会在 0 次转账后查看余额,或者在所有 1,000 次转账之后,或在 639 次转账之后查看余额,谁知道呢。 Doing the synchronization correctly will prevent you from seeing a non-zero total balance, but you should still wait for the clerks to finish.正确执行同步将防止您看到非零的总余额,但您仍应等待职员完成。 (Give it a try and if you can't figure it out, post a new question.) (试一试,如果你不明白,请发布一个新问题。)

In your example synchronized only block all thread call to myBank.transferMoney , but it doesn't ensure every threads done on main thread , you can update source code like this:在您的示例中, 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);
        }
    }
}

Thanks a lot for the helpful answers.非常感谢有用的答案。 I modified my code and now it is working as it should:我修改了我的代码,现在它可以正常工作:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM