Nested Synchronized Block

Suppose I have the following classes:

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

For example, I know that we could only change the state of fromAcct and toAcct objects in the transferMoney method. Can we rewrite our method with a single synchronized block?

 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); } } } 
+6
source share
2 answers

If you have a very unusual and special need that I cannot understand, it seems to me that your goal should be to protect the account balance from being damaged by several flows trying to credit or debit the account at the same time.

The way to do this would be as follows:

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

If your goal during a money transfer is to always ensure that both credit and debit actions occur as a single transaction or atomically, then using synchronization is not suitable. Even in a synchronized block, if an exception occurs, you lose the guarantee that both actions will occur atomically.

Implementing the transactions themselves is a very complex topic, so we usually use databases for this.

EDIT : OP asked: What is the difference between my example (one synchronized block multiplex) and your synchronized in the Account class?

This is a fair question. There are several differences. But I would say that the main difference is that, paradoxically, your example over -syncs. In other words, even though you are now using one synchronized block, your performance will actually be much worse, perhaps.

Consider the following example: You have 4 different bank accounts: give them the name A, B, C, D. And now you have 2 money transfers that are initiated at the same time:

  • Transfer money from account A to account B.
  • Transfer money from account C to account D.

I think that you will agree that since 2 money transfers occur on completely different accounts, there should be no harm (without the risk of corruption) when you execute both money transfers at the same time, right?

However, in your example, money transfers can only be done one after another. With mine, both money transfers occur simultaneously, but also safely. I will only block if both money transfers try to β€œtouch” the same accounts.

Now imagine if you use this code to process hundreds, thousands or more of simultaneous money transfers. Then there is no doubt that my example will perform A LOT better than yours, while maintaining thread safety and protecting the account balance.

Essentially, my version of the code conceptually behaves much more like your original 2-synchronous block code. Except for the following enhancements:

  • Potential deadlock scenario fixed.
  • The intention is clearer.
  • Provides better encapsulation. (This means that even if some other code outside the transferMoney method should have tried to debit or credit some amounts, I would still keep the flows safe, and you wouldn’t. I know that you said that it’s not yours case, but with my version, the design absolutely guarantees this)
+4
source

It looks like you want to implement a synchronization transaction. There is nothing in common between them. A transaction ensures the integrity of the operation - it performs all actions or collapses them all back. Synchronization ensures that data changes from only one thread at a time. For example, a transaction guarantees that if you take money from one account and do not put it into another, then the first action is canceled - the money is not withdrawn. Synchronization checks that if two different dudes put 2 banks in the bank at the same time, then the bank will have 4 pennies, not just 2, because your program adds money to one account based on the previous value.

+2
source

Source: https://habr.com/ru/post/988484/


All Articles