|   | 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
	 visitors since 09-Nov-98