4.2 Thread-Safe Classes

When designing a class that may be used for concurrent programming that is, a class whose instances may be used by more than one thread at a time it is imperative that you make sure the class is "thread-safe." Consider the IntList class of Example 2-7. This class is not thread safe. Imagine what could happen if one thread called clear( ) while another thread was calling add( ). If the clear( ) method sets the list size to 0 after add( ) has read the list size but before it has stored the incremented list size back into the size field of the IntList, it may appear as if the call to clear( ) never happened! In general, a thread-safe class ensures that no thread can ever observe its instances in an inconsistent state.

There are several approaches to thread safety. A particularly simple one is to design immutable classes: if the state of an object can never change, then no thread can ever observe the object in an inconsistent state. Some classes, such as IntList, must be mutable, however. To make these classes thread-safe, you must prevent concurrent access to the internal state of an instance by more than one thread. Because Java was designed with threads in mind, the language provides the synchronized modifier, which does just that. When an instance method is declared synchronized, a thread must obtain a lock on the instance before it calls the method. If the lock is already held by another thread, the thread blocks until it can obtain the lock it needs. This ensures that only one thread may call any of the synchronized methods of the instance at a time.

Example 4-2 is a simplified version of the IntList class of Example 2-7 whose methods have been declared synchronized. This prevents two threads from calling the add( ) method at the same time, and also prevents a thread from calling clear( ) while another thread is calling add( ). The synchronized keyword can also be applied to arbitrary blocks of code within a method, simply by specifying the object to be locked before the code is executed. The ThreadSafeIntList( ) copy constructor uses this technique to synchronize access to the internal state of the object it is copying.

Note that it is not good design to declare every method of every class synchronized. Calling a synchronized method is substantially slower than calling a nonsynchronized one because of the overhead of object locking. The java.util.Vector class that shipped with the original version of Java has synchronized methods to guarantee thread safety. But most applications do not require thread safety, and Java 1.2 provided the more efficient unsynchronized alternative java.util.ArrayList.

Example 4-2. ThreadSafeIntList.java
package je3.thread; /**  * A growable array of int values, suitable for use with multiple threads.  **/ public class ThreadSafeIntList {     protected int[  ] data;    // This array holds the integers     protected int size;      // This is how many it current holds     // Static final values are constants.  This one is private.     private static final int DEFAULT_CAPACITY = 8;     // Create a ThreadSafeIntList with a default capacity     public ThreadSafeIntList( ) {         // We don't have to set size to zero because newly created objects         // automatically have their fields set to zero, false, and null.         data = new int[DEFAULT_CAPACITY];  // Allocate the array     }     // This constructor returns a copy of an existing ThreadSafeIntList.     // Note that it synchronizes its access to the original list.     public ThreadSafeIntList(ThreadSafeIntList original) {         synchronized(original) {             this.data = (int[  ]) original.data.clone( );             this.size = original.size;         }     }     // Return the number of ints stored in the list     public synchronized int size( ) { return size; }     // Return the int stored at the specified index     public synchronized int get(int index) {         if (index < 0 || index >= size) // Check that argument is legitimate             throw new IndexOutOfBoundsException(String.valueOf(index));         return data[index];     }     // Append a new value to the list, reallocating if necessary     public synchronized void add(int value) {          if (size == data.length) setCapacity(size*2); // realloc if necessary         data[size++] = value;                         // add value to list     }     // Remove all elements from the list     public synchronized void clear( ) { size = 0; }     // Copy the contents of the list into a new array and return that array     public synchronized int[  ] toArray( ) {          int[  ] copy = new int[size];         System.arraycopy(data, 0, copy, 0, size);         return copy;     }     // Reallocate the data array to enlarge or shrink it.     // Not synchronized, because it is always called from synchronized methods.     protected void setCapacity(int n) {         if (n == data.length) return;                // Check size         int[  ] newdata = new int[n];                  // Allocate the new array         System.arraycopy(data, 0, newdata, 0, size); // Copy data into it         data = newdata;                              // Replace old array     } }


Java Examples in a Nutshell
Java Examples in a Nutshell, 3rd Edition
ISBN: 0596006209
EAN: 2147483647
Year: 2003
Pages: 285

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net