锁定Java中类的所有实例

时间:2019-04-06 14:52:54

标签: java multithreading thread-safety synchronized reentrantlock

我正在实施一个并行银行系统,其中所有操作可以同时运行。我实现了一个线程安全的transferMoney方法,该方法将amount从帐户from转移到to

transferMoney通过以下代码实现:

public boolean transferMoney(Account from, Account to, int amount) {
        if (from.getId() == to.getId()){
            return false;
        }else if(from.getId() < to.getId()) {
            synchronized(to) {
                synchronized(from) {
                    if(from.getBalance() >= amount) {
                        from.setBalance(from.getBalance()-amount);
                        to.setBalance(to.getBalance()+amount);
                    }else {
                        return false;
                    }
                }
            }
        }else {
            synchronized(from) {
                synchronized(to) {
                    if(from.getBalance() >= amount) {
                        from.setBalance(from.getBalance()-amount);
                        to.setBalance(to.getBalance()+amount);
                    }else {
                        return false;
                    }
                }
            }
        }

        return true;
    }

为防止死锁,我已指定始终以相同顺序获取锁。为了确保以相同的顺序获取锁,我使用了ID中唯一的Account

此外,我实现了一种方法,该方法使用以下代码对银行的总金额进行汇总:

public int sumAccounts(List<Account> accounts) {
    AtomicInteger sum = new AtomicInteger();

    synchronized(Account.class) {
        for (Account a : accounts) {
            sum.getAndAdd(a.getBalance());
        }
    }

    return sum.intValue();
}

问题

当我与sumAccounts()同时运行transferMoney()时,即使没有添加任何钱,我最终也会在银行中得到更多(有时更少)的钱。据我了解,如果我通过Account锁定了所有synchronized(Account.class)个对象,我是否应该因为阻止transferMoney()的执行而得到了正确的库金额?

我到目前为止已经尝试过的东西

我尝试了以下操作:

  • 如上所述同步Account.class(无效)
  • for each循环中同步特定帐户(但是,由于交易是同时发生的,因此这当然不是线程安全的)
  • 通过ReentrantLock对象同步这两种方法。可以,但是对性能造成了很大的影响(是顺序代码的三倍)
  • 在类级别同步两个方法。这也可以,但是比顺序运行这些操作要花三倍的时间。

Account.class上的锁定是否不能阻止进一步的transferMoney()执行?如果没有,我该如何解决此问题?

编辑: getBalance()的代码:

public int getBalance() {
        return balance;
}

3 个答案:

答案 0 :(得分:1)

在这种情况下,您可以使用ReadWriteLock。 transferMoney方法将使用读取锁,因此可以同时执行。 sumAccounts方法将使用写锁,因此在执行时,不能从其他线程执行transferMoney(或sumAccounts)。

使用ReentrantLock并在类级别同步这两个方法,其行为将与您声明的相同,因为它们不允许并发执行transferMoney方法。

示例代码:

t

重入锁的公平模式也将比非公平模式执行得更慢。查看文档以了解详细信息。

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html

答案 1 :(得分:1)

如注释中所述,对一个类对象进行锁定不会对该类的所有实例都进行锁定,而只会对代表您的Account类的Class对象进行锁定。该锁与帐户对象上的锁不兼容,因此根本没有进行同步。

可以在您的for循环内(以sumAccounts的形式)对单个Account对象进行锁定,但这不会阻止此类计划的发生:

- sumAccounts locks 'first' Account and reads balance (and releases lock again at end of the synchronized block taking the lock)
- system schedules a moneyTransfer() from 'first' to 'last'
- sumAccounts locks 'last' Account and reads balance, which includes the amount that was just transferred from 'first' and was already included in the sum

因此,如果您也想避免这种情况,那么您也需要同步Account.class上的moneyTransfer()处理(这样就不必再锁定单个对象了)。

答案 2 :(得分:0)

很难检查您的代码,因为我们无法知道您同步的对象帐户在所有功能中是否完全相同。
首先,我们必须同意余额总和和金额转移是否应该同时运行两个操作。
我希望转帐前后的余额总和是相同的。
另外,您在余额总计中使用synchronized(Account.class),这是错误的。您应该在要循环的对象上进行同步。
现在,即使您确实在完全相同的情况下进行协调,您仍然可以拥有以下时间表:

Thread-1 (transfer)  
  locks from  
Thread-2 (sum balance)  
  locks first object in the list and adds the balance to the running sum and moves to next object
Thread-1  
   locks to (which is the object Thread-2) processed
   moves money from => to  

您已经将to与增加前的金额相加,并且可能要根据时间表将from与扣除后的金额相加。

问题是您要在传输中更新2个对象,但只能锁定1个对象。
我建议的是:

  1. 要么在同一个锁上同步两个方法,然后使它们串行运行
  2. 在对象进入transfer方法时设置一些脏标志,如果设置了该标志,请在余额总和中跳过它们,并在完成所有更新后完成总和
  3. 您为什么还要用Java进行此操作?这应该在具有ACID属性的事务中在数据库中发生。