簡體   English   中英

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

[英]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”帳戶中扣除之前,另一個線程可能可以訪問帳戶余額。 如果所有帳戶都從零開始,我們可以有以下事件序列:

  1. 書記員湯姆向帳戶 1 添加了 100 美元。
  2. 主線程總計帳戶余額。
  3. 書記員湯姆從帳戶 2 中扣除了 100 美元。

在第 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM