Dont synchronize across device accesses

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

There are times, however, when no matter how small the method or how crucial the data in it, you should still avoid synchronizing the method. The most common situation is when the method accesses a physical device with uncertain latency. For example, writing a log file to a hard drive is a very bad thing to do within a synchronized method. The reason for this is simple. Suppose were in a scenario with many instances of Account , and they all log their transactions to a log file, [ 3] as shown in Figur e 12- 1 . [ 3] Or to a database. The discussion applies equally to a database. Figure 12-1. Using a singleton logger A simple version of this is quite easy to implement: package com.ora.rmibook.chapter12; import java.io.; public interface Logger { public void setOutputStreamPrintStream outputStream; public void logStringString string; } package com.ora.rmibook.chapter12; import java.io.; public class SimpleLogger implements Logger { private static SimpleLogger _singleton; public synchronized static SimpleLogger getSingleton { if null==_singleton { _singleton = new SimpleLogger ; } return _singleton; } private PrintStream _loggingStrea m; private SimpleLogger { _loggingStream = System.err; a good default value } public void setOutputStreamPrintStream outputStream { _loggingStream = outputStream; } public synchronized void logStringString string { _loggingStream.printlnstring; } } Note that the static method getSingleton must be synchronized. Otherwise, more than one instance of SimpleLogger can be created. Aside from its rather pathetic lack of functionality™ SimpleLogger takes strings and sends them to a log file, which is not the worlds most sophisticated logging mechanism™there is a significant flaw in this object: the account servers must all wait for a single lock, which is held for indefinite amounts of time. This adds a bottleneck to the entire server application. If the hard disk slows down or is momentarily inaccessible e.g., the log file is mounted across the network, the entire application grinds to a halt. In general, the reasons for using threading in the first place imply that having a single lock that all the threads acquire fairly often is a bad idea. In the case of logs or database connections, it may be unavoidable. However, in those cases, we really need to make the time a thread holds a lock as short as possible. One common solution is to wrap the actual logging mechanism in an outer shell, which also implements the Logger interface, as shown in Figur e 12- 2 . Figure 12-2. An improved singleton logger The way this works is that a piece of code calls logString . The wrapper class takes the string that is supposed to be logged and simply places it in a container, after which, the method returns. This is a very fast operation. Meanwhile, the background thread executes the following loop: 1. Sleep for awhile. 2. Wake up and get all the available strings from the wrapper class. 3. Send the strings to the real logging mechanism. 4. Return to step 1. The code for this logger isnt all that complicated either. In the following example, weve implemented the background thread as an inner class: public class ThreadedLogger implements Logger { private SimpleLogger _actualLogger; private ArrayList _logQueue; public ThreadedLoggerSimpleLogger actualLogger { _logQueue = new ArrayList ; new BackgroundThread.start ; } public void setOutputStreamPrintStream outputStream { _actualLogger.setOutputStreamoutp utStream; } public synchronized void logStringString string { _logQueue.addstring; } private synchronized Collection getAndReplaceQueue { ArrayList returnValue = _logQueue; _logQueue = new ArrayList ; return returnValue; } private class BackgroundThread extends Thread { public void run { whiletrue { pause ; logEntries ; } } private void pause { try { Thread.sleep5000; } catch Exception ignored{} } private void logEntries { Collection entries = getAndReplaceQueue ; Iterator i = entries.iterator ; whilei.hasNext { String nextString = String i.next ; _actualLogger.logStringnextString; } } } } We still have the problem in which different account servers may want to write to the log at the same time, and an instance of Account may be forced to wait. Weve simply replaced a potentially slow and high-variance bottleneck with a fast and low-variance bottleneck. This may still be unacceptable for some. If instances of Account are constantly accessing the log, though each individual logString operation is quick, each instance of Account still waits for many other instances to execute logString . For example, suppose that an average of 30 instances of Account are waiting to use the log file. Then the logString method is really 30 times slower than it appears to be at first glance. Think about that sentence again. What I just asserted is this: the real time it takes to log a string is approximately the time it takes to log a string multiplied by the number of accounts waiting to log a string. However, suppose we have a burst of activity. The number of accounts waiting to log strings may increase, which means that every instance of Account becomes slower. This is pretty nasty; our system will slow down by more than a linear factor during burst periods. How nasty is this? Run a profiler and find out. Thats really the only way to tell why a program isnt performing well. If this is a problem, and we need to eliminate the bottleneck entirely, a variant of the preceding technique often works. Namely, we make the following three changes: • Each server has a separate log object into which it stores logging information. • Each of these logs registers with the background thread. • The background thread now visits each of the logs in sequence, getting the entries and sending them to the real logging mechanism. This effectively means that accounts cannot interfere with each other. Each instance of Account writes to a distinct logging object. The instance of Account may be forced to wait momentarily while the background thread gets the entries from its log. However, that wait is bounded and small. See Figur e 12- 3 . Figure 12-3. A more complex logging example This approach to logging and other forms of device access is often called using a batch thread. Our logging code is remarkably similar to our implementation of automatic lock maintenance using a background thread. You may start to see a pattern here.

12.2.3.3 Dont synchronize across secondary remote method invocations