Synchronize around the smallest possible block of code

_singleton.start ; } return _singleton; } private ArrayList _accounts; private Account3_Impl2_LockThread { _accounts = new ArrayList ; } public synchronized void addAccountAccount3 newAccount { _accounts.addnewAccount; } public void run { while true { try { Thread.sleepTHREAD_SLEEP_TIME; } catch Exception ignored {} decrementLockTimers ; } } private synchronized void decrementLockTimers { Iterator i = _accounts.iterator ; while i.hasNext { Account3_Impl2 nextAccount = Account3_Impl2 i.next ; nextAccount.decrementLockTimerTHREAD_SLEEP_TIME; } } }

12.2.3 Minimize Time Spent in Synchronized Blocks

This guideline may seem rather obvious. After all, the whole reason for using the synchronized keyword is to force threads to temporarily suspend execution. If a single thread holds on to a synchronization lock for a very long time, then the other threads will halt for a very long time. This often results in unresponsive applications that feel sluggish™at least to the clients whose threads are halted. However, a tip that says to minimize time may be too abstract a rule of thumb, so lets break that rule down into three very concrete sub-tips: synchronize around the smallest possible block of code, dont synchronize across device accesses, and dont synchronize across secondary remote method invocations.

12.2.3.1 Synchronize around the smallest possible block of code

Of the three concrete sub-rules, this is both the most obvious and the vaguest. The essence of it is looking at each synchronized method and trying to see whether all of the code in that method needs to be synchronized. Consider our synchronization of Account3_Impl2 earlier: public synchronized Money getBalance throws RemoteException, LockedAccountException { checkAccess ; return _balance; } public synchronized void makeDepositMoney amount throws RemoteException, LockedAccountException, NegativeAmountException { checkAccess ; ... } public synchronized void makeWithdrawalMoney amount throws RemoteException, OverdraftException, LockedAccountException, NegativeAmountException { checkAccess ; ... } private void checkAccess throws LockedAccountException { String clientHost = wrapperAroundGetClientHost ; if null==_currentClient { _currentClient = clientHost; } else { if _currentClient.equalsclientHost { throw new LockedAccountException ; } } resetCounter ; return; } The way this works is we synchronize on the three public interface methods. As a result, each call to checkAccess occurs within a synchronized block, and only one thread can execute the checkAccess method at any given time. Suppose we know that any given computer makes only a single request at a time which is, in fact, the case for dedicated ATMs. We may be able to take advantage of this by using only checkAccess to control synchronization. For example, since checkAccess either grants a lock or throws a LockedAccountException , we could simply rewrite this as: public Money getBalance throws RemoteException, LockedAccountException { checkAccess ; return _balance; } ... private synchronized void checkAccess throws LockedAccountException { String clientHost = wrapperAroundGetClientHost ; ... } Now, each time a remote method call is made, checkAccess is called. Because its synchronized, the server-locking mechanism almost works. The only difficulty is that because this is a multithreaded application, the body of an Account3_Impl2 public method could take longer than two minutes. If so, the lock might be released and two clients can access the account simultaneously. We can fix this with one extra boolean flag, _decrementTimerOn , as in the following code: public void makeWithdrawalMoney amount throws RemoteException, OverdraftException, LockedAccountException, NegativeAmountException { checkAccess ; ... _decrementTimerOn= true; } private synchronized void checkAccess throws LockedAccountException { String clientHost = wrapperAroundGetClientHost ; if null==_currentClient { _currentClient = clientHost; } else { if _currentClient.equalsclientHost { throw new LockedAccountException ; } } _decrementTimerOn = false; resetCounter ; return; } protected synchronized void decrementLockTimerint amountTo Decrement { if false == _decrementTimerOn { return; } _timeLeftUntilLockIsReleased -= amountToDecrement; if _timeLeftUntilLockIsReleased 0 { _currentClient = null; } } Heres a summary. As part of the checkAccess method, which is synchronized, _decrementTimerOn is set to false . Since the method decrementLockTimer is also synchronized, we know that the next time it is called, the thread that calls it will retrieve the new value of _decrementTimerOn and place it in its cache. Hence, it wont actually start the timer going until _decrementTimerOn is set to true once more . Since the only place where _decrementTimerOn is set to true is at the end of the public interface methods, this means the lock wont be relinquished while a public interface method is being executed. The trade-off? We traded a single boolean flag for less code inside the synchronized block. Moreover, we have only two synchronized methods at this point. Weve reduced the number of synchronized blocks of code, which makes the code much easier to understand. Now, its much easier to think about how to remove even more synchronization. The method: private synchronized void checkAccess throws LockedAccountException { String clientHost = wrapperAroundGetClientHost ; if null==_currentClient { _currentClient = clientHost; } else { if _currentClient.equalsclientHost { throw new LockedAccountException ; } } _decrementTimerOn = false; resetCounter ; return; } doesnt need to be fully synchronized. In particular, the method wrapperAround - GetClientHost , which is simply a wrapper around a threadsafe static method in RemoteServer , doesnt need to be synchronized. However, at this point, were reaching diminishing returns; theres a certain value in simply having a few core methods that are entirely synchronized. Remember, these last few rewrites are valid only if client computers send a single request at a time. For example, checkAccess isnt nearly sophisticated enough to differentiate between two clients running at the same IP address. If we need to distinguish between two such clients, the client will probably have to pass in a unique identifier well actually do this when we discuss testing in Chapt er 13 . In general, reducing the number of synchronization blocks often involves making assumptions about client behavior. Its a good idea to document those assumptions.

12.2.3.2 Dont synchronize across device accesses