开发者

Understanding synchronized block in Java

I am trying to understand the concept of synchronizing on an object. Using this example from the Java Cert Book, can you help me understand the difference in behavior between the following 2 pieces of code (one where we synchronize on the object whose methods/operations we want to protect from the race condition and another where we use a helper object as a lock to achieve the same goal):

1.

class Client {
  BankAccount account;

  public void updateTransaction() {
    synchronized (account) {     
      account.update();          // update is safe because of lock on account obj
    }
  }

  public double withdrawFunds() {
     double amt;
     synchronized (开发者_JAVA技巧account) {
       account.calculateInterest();
       amt= account.withdraw();
     }
     return amt;
  }
}

2.

class Client {
  BankAccount account;
  Object lock = new Object();

  public void updateTransaction() {
    synchronized (lock) {     
      account.update();          // update is safe because of a lock        
    }
  }

  public double withdrawFunds() {
     double amt;
     synchronized (lock) {
       account.calculateInterest();
       amt= account.withdraw();
     }
     return amt;
  }
}


Both of those will throw NullPointerExceptions, but only in different places.

You should always synchronize on an immutable reference that will never be null (final).

I guess the answer to your question is that account will potentially change for a given client (assuming there is a setter) but lock never should. Again, the only way to guarantee that is to mark it final.


A better approach is to avoid exposing locking when it can be encapsulated.

class Client {
  final BankAccount account;

  public void updateTransaction() {
      account.update();          // update is synchonized.
  }

  public double withdrawFunds() {
     return account.calculateInterestAndWithdraw();
  }
}


The difference is with respect to what other threads are doing in your system. In the first case, if there are any reasons why some other logic (defined in some other code) should not be exercised concurrently with the updateTransaction method, that logic could also be synchronized on the account object. Conversely, you might have a false synchronization if some unrelated code also uses account as the lock when it has nothing to do with updateTransaction.

In the second case, the lock object is only visible to the Client class, so you are guaranteed that the only synchronization that goes on is what you have specified in this class.

I agree with Peter Lawrey that the best approach is to encapsulate the synchronization logic in a single place where it makes sense, and use private/protected visibility lock objects for that.


In those two cases, there is no difference, but a stylistic difference.

If account could be changed, there is a potential problem that could be synchronizing on the wrong object. Make that property final and you're good to go.


The difference is who holds the Lock.

The first case is the better, because the same reference to account could be in also shared in other thread, for example in the class Manager, and if this manager needs to access the account, should try to hold the lock himself in any case that the Client thread tries to access the account at the same time.

In the second code, the Manager needs to lock on the lock object, for preventing doing damage to the data, so its needs to get the account and the lock.

The best approach is to encapsulate the lock inside account.


I would use option 2, just make the lock object final. The account object can probably be changed, especially if the client can have more than one account. If you use a final lock object you will ensure that any change to the account is synchronized, even if the account object changes to a different instance.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜