当前位置: 首页 > 知识库问答 >
问题:

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

晋鹤轩
2023-03-14

我们在银行有100个账户和两个办事员,作为线程实现,他们使用同步方法transferMoney将每1000倍的资金从accountNumberFrom账户转移到accountNumberTo账户。由于所有账户都以余额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);
    }
}

共有3个答案

端木令雪
2023-03-14

非常感谢你有用的答案。我修改了我的代码,现在它正在正常工作:

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();
    }
}
魏硕
2023-03-14

在你的例子同步的只阻止所有线程调用myBank.transferMoney,但它不能确保每个线程都在主线程上完成,你可以这样更新源代码:

java prettyprint-override">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);
        }
    }
}
任飞龙
2023-03-14

一个问题是,synchronizedtransferMoney方法只考虑一个帐户,因此在将转账金额添加到“to”帐户之后,但在从“from”帐户扣除之前,另一个线程可能会访问帐户余额。如果所有账户都从零开始,我们可能会有以下事件序列:

  1. 职员汤姆在1号账户上加了100美元
  2. 主线程合计帐户余额
  3. 职员汤姆从2号账户中扣除100美元

在第2步,我们看到所有账户的总数是100美元,而不是零。

因此,在持有同步锁的同时,transferMoney方法更新两个帐户非常重要。

另一个问题是,transferMoney是同步的,但是合计账户余额的代码(上面的步骤2)不是同步的。因此,即使您在transferMoney方法中更新了两个帐户,上面的事件序列仍然可能发生,因为主线程在执行步骤2之前不同步。

我会将汇总账户的代码移动到银行,并使其同步。这将使这两种方法在Bank实例上同步,并防止出现错误的事件序列。

第二个问题是,在主线程中,您不必等待职员完成他们的转移。您的代码正在执行所有1000次传输,但您只需在启动职员线程后立即检查余额,因此您可能会在0次传输后,或在所有1000次传输后,或639次传输后查看余额,谁知道呢。正确地执行同步将阻止您看到非零的总余额,但您仍应等待事务员完成。(试一试,如果你想不出来,就发一个新问题。)

 类似资料:
  • 问题内容: 该练习直接由Kathy Seirra和Bert Bates撰写的SCJP 同步代码块 在本练习中,我们将尝试同步代码块。在该代码块中,我们将获得对对象的锁定,以便在代码块执行时其他线程无法修改它。我们将创建三个线程,它们都将尝试操纵同一对象。每个线程将输出一个字母100次,然后将该字母加1。我们将使用的对象是StringBuffer。 我们可以在String对象上进行同步,但是一旦创建

  • 是否有在java中使用Promise(就像在JavaScript中使用ut一样)而不是使用嵌套回调的概念? 如果是这样,是否有一个在java中实现回调和链接处理程序的示例?

  • 本文向大家介绍进一步理解Java中的多态概念,包括了进一步理解Java中的多态概念的使用技巧和注意事项,需要的朋友参考一下 多态性有两种: 1)编译时多态性 对于多个同名方法,如果在编译时能够确定执行同名方法中的哪一个,则称为编译时多态性. 2)运行时多态性 如果在编译时不能确定,只能在运行时才能确定执行多个同名方法中的哪一个,则称为运行时多态性. 方法覆盖表现出两种多态性,当对象获得本类实例时,

  • 从 Java 5 版本之后可以在源代码中嵌入一些补充信息,这种补充信息称为 注解(Annotation),是 Java 平台中非常重要的一部分。注解都是 @ 符号开头的,例如我们在学习方法重写时使用过的 @Override 注解。同 Class 和 Interface 一样,注解也属于一种类型。 Annotation 可以翻译为“注解”或“注释”,一般翻译为“注解”,因为“注释”一词已经用于说明“

  • 主要内容:什么是输入/输出流,输入流,输出流在 Java 中所有数据都是使用流读写的。 流是一组有序的数据序列,将数据从一个地方带到另一个地方。 根据数据流向的不同,可以分为输入(Input)流和输出(Output)流两种。 在学习输入和输出流之前,我们要明白为什么应用程序需要输入和输出流。例如,我们平时用的 Office 软件,对于 Word、Excel 和 PPT 文件,我们需要打开文件并读取这些文本,和编辑输入一些文本,这都需要利用输

  • 本文向大家介绍object-c中的协议和java中的接口概念有何不同?相关面试题,主要包含被问及object-c中的协议和java中的接口概念有何不同?时的应答技巧和注意事项,需要的朋友参考一下 答案:OC中的协议有2层含义,官方定义为 formal和informal protocol。前者和Java接口一样。 informal protocol中的方法属于设计模式考虑范畴,不是必须实现的,但是如