嵌套的同步块

Iur*_*rii 6 java concurrency multithreading 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
      }
 }
}
Run Code Online (Sandbox Code Playgroud)

sst*_*tan 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您在汇款期间的目标是始终确保信用和借记操作既作为一个交易发生,也可以原子地发生,那么使用同步不是正确的方法.即使在同步块中,如果发生异常,那么您将失去保证两个动作将以原子方式发生.

自己实现事务是一个非常复杂的主题,这就是我们通常使用数据库为我们这样做的原因.

编辑:OP问:我的示例(一个同步块多路复用器)与您在Account类中同步的示例有什么区别?

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

请考虑以下示例:您有4个不同的银行帐户:让我们将它们命名为A,B,C,D.现在您有2个汇款在同一时间启动:

  1. 从账户A到账户B的汇款.
  2. 从账户C到账户D的汇款.

我认为你会同意,因为2次转账是在完全独立的账户上进行的,所以在同时执行两次转账时不应该有任何损害(没有腐败风险),对吧?

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

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

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

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