为了账号安全,请及时绑定邮箱和手机立即绑定

在 Java 中锁定类的所有实例

在 Java 中锁定类的所有实例

慕姐8265434 2022-09-22 16:28:03
我正在实施一个并行银行系统,其中所有操作都可以同时运行。我已经实现了一个线程安全的方法,从帐户传输到。transferMoneyamountfromtotransferMoney使用以下代码实现: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;    }为了防止死锁,我指定始终以相同的顺序获取锁。为了确保以相同的顺序获取锁,我使用的唯一 .IDAccount此外,我还实现了一个方法,该方法使用以下代码汇总了银行中的总金额: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()Accountsynchronized(Account.class)transferMoney()
查看完整描述

3 回答

?
收到一只叮咚

TA贡献1821条经验 获得超4个赞

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


锁定单个帐户对象可以在您的 for 循环(在 sumAccounts 中)内完成,但这不会阻止这样的计划发生:


- 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

因此,如果您也想防止这种情况,您也需要同步帐户上的moneyTransfer()处理.class(然后,这就过时了锁定个人对象的需要)。


查看完整回答
反对 回复 2022-09-22
?
MMMHUHU

TA贡献1834条经验 获得超8个赞

对于这种情况,您可以使用读写锁定。传输货币方法将使用读锁定,因此可以并发执行。sumAccounts 方法将使用写锁定,因此当它执行时,不能从其他线程执行任何传输货币(或 sumAccounts)。


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


示例代码:


final ReadWriteLock rwl = new ReentrantReadWriteLock();


public boolean transferMoney(Account from, Account to, int amount) {

  rwl.readLock().lock();

  try{

    .... Your current code here

  }

  finally {

       rwl.readLock().unlock();

  }

}


public int sumAccounts(List<Account> accounts) {

  rwl.writeLock().lock();

  try{

    // You dont need atomic integer here, because this can be executed by one thread at a time

    int sum = 0;

    for (Account a : accounts) {

        sum += a.getBalance();

    }

    return sum;

  }

  finally {

       rwl.writeLock().unlock();

  }

}

此外,重入锁的公平模式往往比非公平模式执行得更慢。有关详细信息,请查看文档。


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


查看完整回答
反对 回复 2022-09-22
?
jeck猫

TA贡献1909条经验 获得超7个赞

查看代码非常困难,因为我们无法知道同步的对象帐户是否在所有函数中都是完全相同的实例。

首先,我们必须同意余额之和金额的转移是否是两个应该同时运行的操作。

我预计在金额转移之前和之后的余额总和是相同的。

此外,您正在使用余额的总和,这是错误的。您应该在要循环访问的对象上进行同步。

现在,即使您确实在完全相同的实例中进行协调,您仍然可以有以下计划: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  

您已经在增加之前用金额相加,并且根据计划,您可以在扣除后添加金额。tofrom


问题是您要在传输中更新 2 个对象,但总和中只锁定了 1 个对象。

我的建议是:

  1. 在同一个锁上同步两种方法,并使它们串行运行

  2. 当对象进入方法时设置一些脏标志,如果设置了,则在余额之和中跳过它们,并在所有更新完成后完成总和transfer

  3. 你为什么还要在Java中这样做?这应该在数据库中使用具有 ACID 属性的事务进行。


查看完整回答
反对 回复 2022-09-22
  • 3 回答
  • 0 关注
  • 95 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信