- 213 - static synchronizations. This is not strictly speaking a bug, but certainly not optimal
for performance.
The second cost of synchronization is in what it actually does. Synchronization serializes execution of a set of statements so that only one thread at a time executes that set. Whenever multiple threads
simultaneously try to execute the same synchronized block, those threads are all effectively run together as one single thread. This completely negates the purpose of having multiple threads and is
potentially a huge bottleneck in any program. On machines with multiple CPUs, you can leave all but one CPU idle when serialized execution occurs. The later section
Section 10.4.2 addresses
techniques for avoiding serialized execution where possible.
10.4.1 Desynchronization and Synchronized Wrappers
As we just noted, synchronized methods have performance costs. In fact, for short methods, using a synchronized method can mean that the basic time involved in calling the method is significantly
larger than the time for actually running it. The overhead of calling an unsynchronized method can be much smaller than that of calling a synchronized method.
You should be aware of when you do not need to synchronize. Read-only objects never need synchronization. Stateless objects including no-static state almost never need synchronization on
their methods. There are certain unusual implementations when methods may be altering state directly in another shared object, where synchronization would be required. Some objects with
state may have no need for synchronization because access to the object is highly restricted, and the synchronization is handled by other objects. Some objects can implement a copy-on-write
mechanism
StringBuffer
uses this; see Chapter 5
. You can define copy-on-write in such a way to allow multithreaded updates of that object.
Many multithreaded applications actually use most of their objects in a single-threaded manner. Each individual thread maintains its own references to most objects, with just a few data or utility
objects actually being used by multiple threads. From a performance standpoint, it seems a shame to have the overhead of synchronized objects on many classes where synchronization is not needed or
used. On the other hand, when you design and build a particular class, it is seldom possible to anticipate that it will never be shared among several threads, so to be on the safe side, typically the
class is built with synchronization.
When you have identified a bottleneck that uses synchronized objects, you can sometimes remove synchronization on those objects by giving different threads their own unsynchronized copies of
those objects. This is especially easy to achieve when you use objects that have an unsynchronized implementation held in a synchronized wrapper .
The idea behind synchronized wrappers is that you build your class completely unsynchronized, as if it is to be used single-threaded. But you also provide a wrapper class with exactly the same
interface. The difference in the wrapper class is that all methods that require synchronization are defined with the
synchronized
modifier. The wrapper could be a subclass with methods reimplemented, but more typically, it is a separate class that holds an internal reference to an
instance of the unsynchronized class and wraps all the methods to call that internal object. Using synchronized wrappers allows you the benefits of thread-safe objects by default, while still retaining
the capability to selectively use unsynchronized versions of those classes in bottlenecks.
From Java 2, the framework of using synchronized wrappers has become standard. All the new collection classes in
java.util
are now defined unsynchronized, with synchronized wrappers available for them. Old collection classes e.g.,
Hashtable
,
Vector
that are already synchronized
- 214 - remain so for backward compatibility. The wrappers are usually generic, so you can actually create
wrapped synchronized objects from any object of the right type.
I include a short example of the synchronized-wrapper framework here for clarity. If class
UnsyncedAdder
is defined as follows:
public interface Adder { public void addint aNumber;
} public class UnsyncedAdder
implements Adder {
int total; int numAdditions;
public void addint aNumber {total += aNumber; numAdditions++;} }
Then the synchronized wrapper for this class can be:
public class SyncedAdder implements Adder
{ Adder adder;
public SyncedAdderAdder a {adder = a;} public synchronized void addint aNumber { adder.addaNumber;}
}
Obviously, you refer to
Adder
objects in your code; dont refer explicitly to concrete implementations of
Adder
classes such as
UnsyncedAdder
and
SyncedAdder
except in the constructor or factory classes. Note that the synchronized wrapper is completely generic. It wraps
any implementation of
Adder
, providing synchronization on the
add
method irrespective of the underlying concrete implementation of the
Adder
class. Using unsynchronized classes gives a performance advantage, but it is a maintenance drawback.
There is every likelihood that initial implementation of any application will use the unsynchronized classes by default, leading to many subtle threading bugs that can be a debugging and maintenance
nightmare. Typical development scenarios then try to identify which objects need to be synchronized for the application, and then wrap those objects in their synchronized wrappers.
Under the stress of project milestones, I know of one project where the developers went through all their code with a recursive routine, chopping out every
synchronized
keyword in method declarations. This seemed quicker than carefully tuning the code, and did in fact give a performance
improvement. They put a few
synchronized
keywords back in after the regression tests. This type of tuning is exactly the opposite of what I recommend.
Instead, you should use synchronized wrapped objects throughout the application by default, but ensure that you have the capability to easily replace these with the unsynchronized underlying
objects. Remember, tuning is better done after the application works correctly, not at the beginning. When you come to tune the application, identify the bottlenecks. Then, when you find
that a particular class needs to be speeded up, determine whether that class can be used unsynchronized. If so, replace it with its unsynchronized underlying object, and document this
thoroughly. Any changes in the application must reexamine these particular tuning changes to ensure that these objects do not subsequently need to become synchronized.
[1]
- 215 -
[1]
When the design indicates that a class or a set of methods should definitely be synchronized or definitely does not need synchronization, then of course you should apply that design decision. For example, stateless objects can often be specified with no synchronization. However, there are many classes and methods
where this decision is uncertain, and this is where my recommendation applies.
Be aware, though, that there is no win-win situation here. If you tend towards unsynchronized classes by default, you leave your application open to corruption. If you prefer my recommended
synchronized by default approach, your application has an increased chance of encountering deadlocks . On the basis that deadlocks are both more obvious and easier to fix than corrupt objects,
I prefer the synchronized by default option. Implementing with interfaces and synchronized wrappers gives you an easy way to selectively back out of synchronization problems.
The next test gives you an idea of the relative performance of synchronized and unsynchronized methods, and of synchronized wrappers. The test compares synchronized
Vector
, unsynchronized
ArrayList
, and synchronized wrapper
ArrayList
wrapped classes:
package tuning.threads; import java.util.;
public class ListTesting {
public static final int CAPACITY = 100000; public static void mainString args[]
{ In order to isolate the effects of synchronization, we make sure
that the garbage collector doesnt interfere with the test. So we use a bunch of pre-allocated, pre-sized collections, and
populate those collections with pre-existing objects. No objects will be created or released during the timing phase of the tests.
List[] l = {new VectorCAPACITY, new VectorCAPACITY, new VectorCAPACITY, new ArrayListCAPACITY,
new ArrayListCAPACITY, new ArrayListCAPACITY, Collections.synchronizedListnew ArrayListCAPACITY,
Collections.synchronizedListnew ArrayListCAPACITY, Collections.synchronizedListnew ArrayListCAPACITY};
int REPEAT = args.length 0 ? Integer.parseIntargs[0] : 100; Vary the order.
testl[0], REPEAT, Vector; testl[6], REPEAT, sync ArrayList ;
testl[3], REPEAT, ArrayList; testl[1], REPEAT, Vector;
testl[4], REPEAT, ArrayList; testl[7], REPEAT, sync ArrayList ;
testl[2], REPEAT, Vector; testl[5], REPEAT, ArrayList;
testl[8], REPEAT, sync ArrayList ; }
public static void testList l, int REPEAT, String ltype {
need to initialize for set to work. Dont measure this time for int j = 0; j CAPACITY; j++
l.addBoolean.FALSE; long time = System.currentTimeMillis ;
The test sets elements repeatedly. The set methods are very similar. Apart from synchronization, the Vector.set
is slightly more efficient than the ArrayList.set , which is in turn more efficient than the wrapped ArrayList because
there is one extra layer of method calls for the wrapped object. for int i = REPEAT; i 0; i--
- 216 -
for int j = 0; j CAPACITY; j++ l.setj, Boolean.TRUE;
System.out.printlnltype + took + System.currentTimeMillis -time;
} }
The normalized results from running this test are shown in Table 10-3
. Table 10-3, Timings of the Various Array-Manipulation Tests, Normalized to the JDK 1.2
Vector Test
1.2 1.2 no JIT