CS 596 Java Programming Fall Semester, 1998 Threads part 4 |
||
---|---|---|
© 1998, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 21-Dec-98 |
public int[] arrayPartialSums( int[] input ) { for ( int k = 1; k < input.length; k ++ ) input[k] = input[ k - 1] + input[ k ]; return input; }In the method below, even if all the methods of Foo are synchronized another thread can change the state of aFoo while objectMethod is executing.
public Object objectMethod( Foo aFoo ) { aFoo.bar(); aFoo.process(); return aFoo().getResult(); }
public Object objectMethod( Foo aFoo ) { synchronized ( aFoo ) { aFoo.bar(); aFoo.process(); return aFoo().getResult(); } }
public int[] arrayPartialSums( int[] input ) { int[] inputClone; synchronized (input) { inputClone = input.clone(); } for ( int k = 1; k < input.length; k ++ ) inputClone [k] = inputClone [ k - 1] + inputClone [ k ]; return inputClone; }
public void callerMethod() { // blah aWidget.arrayPartialSums( intArray.clone() ) }
public void callerMethod() { // blah aWidget.arrayPartialSums( intArray ) intArray = null; }
Returner nulls its Reference
If a method nulls out its copy of values it returns or returns a clone, it will reduce the problem of two threads accessing the same reference.
public Foo aMethod() { Foo localVarCopy = theRealFooReference; theRealFooReference = null; return localVarCopy; } public Foo aMethod() { return theRealFooReference.clone(); }Immutable Objects
Designing classes so the state of the object can not be modified eliminates the problem of multiple threads modifying objects state. String is an example of this.
A weaker idea is to create read-only copies of existing objects. An even weaker idea is to create read-only wrappers for existing objects. The later can be strengthened by using in with the clone method. The following two slides illustrate read-only objects.
Read-Only Copies - Inheritance Version
public class Point { int x; int y; public Point( int x , int y ) { this.x = x; this.y = y; } public int y() { return y; } public void y( int newY) { y = newY; } public int x() { return x; } public void x( int newX) { x = newX; } } public class ReadOnlyPoint extends Point { public ReadOnlyPoint( int x, int y ) { super( x, y ); } public ReadOnlyPoint( Point aPoint ) { super( aPoint.x(), aPoint.y() ); } public void y( int newY ) { throw new UnsupportedOperationException() ; } public void x( int newX ) { throw new UnsupportedOperationException() ; } }
Read-Only Wrappers - Composition Version
interface Point { public int y(); public void y( int newY); public int x(); public void x( int newX); } public class ReadWritePoint implements Point { int x; int y; public ReadWritePoint( int x , int y ) { this.x = x; this.y = y; } public int y() { return y; } public void y( int newY) { y = newY; } public int x() { return x; } public void x( int newX) { x = newX; } } public class ReadOnlyWrapperPoint implements Point { Point myData; public ReadOnlyWrapperPoint( Point aPoint ) {myData = aPoint; } public int y() { return myData.y(); } public int x() { return myData.x(); } public void y( int newY ) { throw new UnsupportedOperationException() ; } public void x( int newX ) { throw new UnsupportedOperationException() ; } }
Multiple Versions of Data Structures
We may needs different versions of a data structure that works differently if it is uses sequentially or with threads. On this slide, we have a Stack that is not synchronized for use in sequential programming. Composition is used over inheritance. Since we may need a LinkedListStack class, composition will allow the SynchronizedStack and the WaitingStack to work with LinkedListStack objects.
interface Stack { public void push( float item ); public float pop(); public boolean isEmpty(); public boolean isFull(); } public class ArrayStack implements Stack { private float[] elements; private int topOfStack = -1; public ArrayStack( int stackSize ) { elements = new float[ stackSize ]; } public void push( float item ) { elements[ ++topOfStack ] = item; } public float pop() { return elements[ topOfStack-- ]; } public boolean isEmpty() { if ( topOfStack < 0 ) return true; else return false; } public boolean isFull() { if ( topOfStack >= elements.length ) return true; else return false; } }
The Synchronized Stack
This example provides straightforward synchronization for a Stack object.
public class SynchonizedStack implements Stack { Stack myStack; public SynchonizedStack() { this( new ArrayStack() ); } public SynchonizedStack( Stack aStack ) { myStack = aStack; } public synchonized boolean isEmpty() { return myStack.IsEmpty(); } public synchonized boolean isFull() { return myStack.isFull(); } public synchonized void push( float item ) { myStack.push( item ); } public synchonized float pop() { return myStack.pop(); } }
WaitingStack In sequential programming there is not much that can be done when you attempt to pop() an element off an empty stack. In concurrent programming, we can have the thread that requested the pop() wait until another thread pushes an element on the stack. The stack below does this.
public class WaitingStack implements Stack { Stack myStack; public WaitingStack( Stack aStack ) { myStack = aStack; } public synchonized boolean isEmpty() { return myStack.IsEmpty(); } public synchonized boolean isFull() { return myStack.isFull(); } public synchonized void push( float item ) { myStack.push( item ); notifyAll(); } public synchonized float pop() { while ( isEmpty() ) try { wait(); } catch ( InterruptedException ignore ) {} return myStack.pop(); } }
Background Operations
There are times when we would like to perform operations in the "background". When these operations are done then another thread will use the result of the computations. How do we know when the background thread is done? The polling done here does consume CPU cycles. We could end up with one thread wasting CPU time just checking if another thread is done.
class TimeConsumingOperation extends Thread { Object result; boolean isDone = false; public void run() { DownLoadSomeData&PerformSomeComplexStuff; result = resultOfMyWork; isDone = true; } public Object getResult() { return result; } public boolean isDone() { return isDone(); } } public class Poll { public static void main( String args[] ) { TimeConsumingOperation background = new TimeConsumingOperation(); background.start(); while ( !background.isDone() ) { performSomethingElse; } Object neededInfo = background.getResult(); } }
Futures
One way to handle these "background" operations is to wrap them in a sequential appearing class: a future. When you create the future object, it starts the computation in a thread. When you need the result, you ask for it. If it is not ready yet, you wait until it is ready.
class FutureWrapper { TimeConsumingOperation myOperation; public FutureWrapper() { myOperation = new TimeConsumingOperation(); myOperation.start(); } public Object compute() { try { myOperation.join(); return myOperation.getResult(); } catch (InterruptedException trouble ) { DoWhatIsCorrectForYourApplication; } } }
public class FutureExample { public static void main( String args[] ) { FutureWrapper myWorker = new FutureWrapper(); DoSomeStuff; DoMoreStuff; x = myWorker.compute(); } }
Callbacks
The thread doing the computation can use callbacks to notify other objects that it is done.
class MasterThread { public void normalCallback( Object result ) { processResult; } public void exceptionCallback( Exception problem ) { handleException; } public void someMethod() { compute; TimeConsumingOperation backGround = new TimeConsumingOperation( this ); backGround.start(); moreComputation; } } class TimeConsumingOperation extends Thread { MasterThread master; public TimeConsumingOperation( MasterThread aMaster ) { master = aMaster; } public void run() { try { DownLoadSomeData; PerformSomeComplexStuff; master.normalCallback( resultOfMyWork ); } catch ( Exception someProblem ) { master.exceptionCallback( someProblem ); } } }
Callbacks with Listeners The following code uses Java's standard idea of listeners to generalize the callback process. Anyone that is interested in the results of the thread implements the ThreadListener interface and registers their interest (shown later). The results are passed back in a ThreadEvent object.
public interface ThreadListener { public void threadResult( ThreadEvent anEvent ); public void threadExceptionThrown( ThreadEvent anEvent ); }
public class ThreadEvent extends java.util.EventObject { Exception thrown; Object result; public ThreadEvent( Object source ) { super( source ); } public ThreadEvent( Object source, Object threadResult ) { super( source ); result = threadResult; } public ThreadEvent( Object source, Exception threadException ) { super( source ); thrown = threadException; } public Exception getException() { return thrown; } public Object getResult() { return result; } }
ThreadListenerHandler ThreadListenerHandler is a helper class used to perform the actual broadcast.
public class ThreadListenerHandler { ArrayList listeners = new ArrayList(); Object theListened; public ThreadListenerHandler( Object listened ) { theListened = listened; } public synchronized void addThreadListener( ThreadListener aListener ) { listeners.add( aListener ); } public synchronized void removeThreadListener( ThreadListener aListener ) { listeners.remove( aListener ); } public void broadcastResult( Object result ) { Iterator sendList; synchronized ( this ) { sendList = ( (ArrayList ) listeners.clone()).iterator(); } ThreadEvent broadcastData = new ThreadEvent( theListened, result ); while ( sendList.hasNext() ) { ThreadListener aListener = (ThreadListener) sendList.next(); aListener.threadResult( broadcastData ); } } public void broadcastException( Exception anException ) { Iterator sendList; synchronized ( this ) { sendList = ( (ArrayList ) listeners.clone()).iterator(); } ThreadEvent broadcastData = new ThreadEvent( theListened, anException); while ( sendList.hasNext() ) { ThreadListener aListener = (ThreadListener) sendList.next(); aListener.threadExceptionThrown( broadcastData ); } } }
TimeConsumingOperation The methods addThreadListener and removeThreadListener are used by client code to register interest in "listening" to this thread.
class TimeConsumingOperation extends Thread { ThreadListenerHandler listeners = new ThreadListenerHandler( this ); public void addThreadListener( ThreadListener aListener ) { listeners.addThreadListener( aListener ); } public void removeThreadListener( ThreadListener aListener ) { listeners.removeThreadListener( aListener ); } public void run() { try { DownLoadSomeData; PerformSomeComplexStuff; listeners.broadcastResult( null ); } catch ( Exception someProblem ) { listeners.broadcastException( someProblem ); } } }
MasterThread Here we can see how the creator of TimeConsumingOperation works.
class MasterThread implements ThreadListener { public void threadResult( ThreadEvent threadResult ) { // Get the results and use them to do perform the task threadResult.getResult(); } public void threadExceptionThrown( ThreadEvent problem ) { // The other thread ended in an exception, deal with that here problem.getException(); } public void someMethod() { compute; TimeConsumingOperation backGround = new TimeConsumingOperation( ); // Register interest in the background's results backGround.addThreadListener( this ); backGround.start(); moreComputation; } }
Using an Adapter
Sometimes you may not want your class to implement the ThreadListener interface. Other method names and parameter types may be more appropriate for your context. We can use an "adapter" class to adapt the methods in the MasterThread class to the methods in the ThreadListener interface. This use of anonymous classes is a major motivation for adding anonymous classes to Java.
class MasterThread { public void compute( String data ) { UseStringToPerformComputation } public void handleException( Exception problem ) { HandleTheException } public void someMethod() { TimeConsumingOperation backGround = new TimeConsumingOperation( ); backGround.addThreadListener( new ThreadListener() { public void threadResult( ThreadEvent anEvent ) { compute( (String) anEvent.getResult() ); } public void threadExceptionThrown(ThreadEvent anEvent ) { handleException( anEvent.getException() ); } } ); backGround.start(); moreComputation; } }
Copyright © 1998 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
All rights reserved.
visitors since 09-Nov-98