嵌套的同步块

时间:2015-06-03 23:03:59

标签: java multithreading concurrency synchronized

让我们想象我有下一堂课:

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;
  }
}

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

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);
      }
 }
}

2 个答案:

答案 0 :(得分:5)

除非您有一个我不能理解的非常不寻常和特殊的需求,否则在我看来,您的目标应该是保护帐户余额免受多个线程的损害,这些线程试图在帐户中贷记或借记帐户。同一时间。

这样做的方法是这样的:

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问:我的示例(一个已同步的块多路复用器)与您在Account类中同步的示例有什么区别?

这是一个公平的问题。有一些差异。但我要说的是,讽刺的是,你的例子 over - 同步。换句话说,即使您现在使用单个同步块,您的性能实际上也会更糟糕,可能。

考虑以下示例: 您有4个不同的银行帐户:我们将其命名为A,B,C,D。 现在,你有两笔资金转账,在同一时间启动:

  1. 从帐户A到帐户B的汇款。
  2. 从帐户C到帐户D的汇款。
  3. 我认为你会同意,因为2次转账是在完全独立的账户上进行的,所以在同时执行这两笔转账时不应有任何损害(没有腐败风险),对吗?

    然而,以你的例子为例,汇款只能一个接一个地执行。在我的情况下,两种资金转移同时发生,但也是安全的。如果两次汇款都试图触及"相同的帐户。

    现在假设您使用此代码处理数百,数千或更多并发转账。毫无疑问,我的例子将比你的表现更好,同时仍保持线程安全,并保护账户余额的正确性。

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

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

答案 1 :(得分:2)

看起来您希望通过同步实现Transaction。他们之间没有什么共同之处。事务提供操作的完整性 - 执行所有操作或将其全部回滚。同步确保数据一次只能从一个线程更改。例如,交易确保如果您从一个帐户中取款而不将其转移到另一个帐户,那么第一个操作就是撤销 - 金钱不会被取消。同步检查,如果两个不同的家伙在同一时刻向银行投入2便士,那么银行将有4便士,而不仅仅是2,因为您的计划根据之前的价值向同一帐户添加资金。