嵌套同步块

Nested synchronized block

假设我有下一个 类:

public class Service {
    public void transferMoney(Account fromAcct, Account toAcct, int amount) {
      synchronized (fromAcct) {
        synchronized (toAccount) { // could we use here only one synchronized block?
            fromAcct.credit(amount);
            toAccount.debit(amount);
        }
      }
    }
}

class Account {
  private int amount = 0;

  public void credit(int sum) {
    amount = amount + sum;
  }

  public void debit(int sum) {
    amount = amount - sum;
  }
}

例如,我知道我们只能在 transferMoney 方法中更改 fromAccttoAcct 对象的状态。那么我们可以用一个 synchronized 块重写我们的方法吗?

public class Service {
 private final Object mux = new Object();

 public void transferMoney(Account fromAcct, Account toAcct, int amount) {
      synchronized(mux) {
        fromAcct.credit(amount);
        toAcct.debit(amount);
      }
 }
}

看来你是想通过同步来实现Transaction。他们之间没有任何共同点。事务提供操作的完整性——执行所有操作或将它们全部回滚。同步确保一次仅从一个线程更改数据。例如,交易确保如果您从一个帐户中取钱而不是将它们存入另一个帐户,那么第一个操作是撤消 - 钱不会被提取。同步检查如果两个不同的家伙在同一时刻将 2 便士存入银行,那么银行将有 4 便士而不是只有 2 便士,因为您的程序会根据先前的值将钱添加到同一帐户。

除非您有我无法理解的非常不寻常和特殊的需求,否则在我看来,您的目标应该是保护帐户余额不被多个线程破坏,这些线程试图在同时.

这样做的方法是这样的:

public class Service {
    public void transferMoney(Account fromAcct, Account toAcct, int amount) {
        fromAcct.credit(amount);
        toAccount.debit(amount);
    }
}

class Account {
    private final object syncObject = new Object();
    private int amount = 0;

    public void credit(int sum) {
        synchronized(syncObject) {
            amount = amount + sum;
        }
    }

    public void debit(int sum) {
        synchronized(syncObject) {
            amount = amount - sum;
        }
    }
}

如果您在汇款过程中的目标是始终确保贷方和借方操作作为一个事务发生,或者以原子方式发生,那么使用同步不是正确的方法。即使在同步块中,如果发生异常,那么您就失去了两个操作将自动发生的保证。

自己实现交易是一个非常复杂的话题,这就是为什么我们通常使用数据库来为我们做这件事。

编辑OP 问:我的示例(一个同步块 mux)和您在帐户 class 中同步的示例有什么区别?

这是一个公平的问题。有一些差异。但我要说的是,主要区别在于,具有讽刺意味的是,您的示例 over-同步。换句话说,即使您现在使用单个同步块,您的性能实际上可能会更差。

考虑以下示例: 您有 4 个不同的银行账户:我们将它们命名为 A、B、C、D。 现在您有 2 笔同时发起的汇款:

  1. 从账户 A 向账户 B 转账。
  2. 从账户 C 转账到账户 D。

我认为您会同意,因为两次汇款是在完全独立的账户上进行的,所以同时执行两次汇款应该没有危害(没有腐败风险),对吗?

然而,对于您的示例,汇款只能一个接一个地执行。在我的情况下,两种汇款同时发生,但也很安全。只有当两次汇款都试图"touch"相同的账户时,我才会阻止。

现在想象一下,如果您使用此代码处理数百、数千或更多的并发汇款。那么毫无疑问,我的示例将比您的示例执行得更好,同时仍然保持线程安全,并保护帐户余额的正确性。

实际上,我的代码版本在概念上表现得更像您原来的 2 同步块代码。除了以下改进:

  • 修复了潜在的死锁情况。
  • 意图更清晰。
  • 提供更好的封装。 (这意味着即使 transferMoney 方法之外的一些其他代码试图借记或贷记一些金额,我仍然会保持线程安全,而你不会。我知道你说过这不是你的情况,但是我的版本,设计绝对保证)