SDSU CS 596 Java Programming
Fall Semester, 1998
Threads part 4
To Lecture Notes Index
© 1998, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 21-Dec-98

Contents of Doc 22, Threads part 4


References


Concurrent Programming in Java: Design Principles and Patterns , Doug Lea, Addison-Wesley, 1997

The Java Programming Language , 2 nd Ed. Arnold & Gosling, Addison-Wesley, 1998


Listen Here!S-nov11 5mins Doc 22, Threads part 4 Slide # 2

Some Thread Ideas

Passing Data


When we pass data in or out of a method, there are problems with the data being changed by another thread while the method is using the data.

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();
}

Lock the Data

If all users of aFoo follow the convention of locking the object before using it, then a Foo will not change in objectMethod due to activities in other threads.

public Object objectMethod( Foo aFoo ) {
   synchronized ( aFoo ) {
      aFoo.bar();
      aFoo.process(); 
      return aFoo().getResult();
   }
}


Listen Here!S-nov11 3mins Doc 22, Threads part 4 Slide # 3

Clone the Data in the Method


Creating a clone helps insure that the local copy will not be modified by other threads. Of course, you need to perform a deep copy to insure no state is modified by other threads.

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;
}

Pass in a Clone

public void callerMethod() {
   // blah
   aWidget.arrayPartialSums( intArray.clone() )
}
Passer nulls its Reference

If the calling method removes its copy of parameters, then there should only be one copy of the parameter.

public void callerMethod() {
   // blah
   aWidget.arrayPartialSums( intArray )
   intArray = null;
}

Listen Here!S-nov11 3mins Doc 22, Threads part 4 Slide # 4
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.

Doc 22, Threads part 4 Slide # 5
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() ;
   }
}

Doc 22, Threads part 4 Slide # 6
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() ;
   }
}

Listen Here!S-nov11 58secs Doc 22, Threads part 4 Slide # 7

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;
   }
}

Listen Here!S-nov11 21secs Doc 22, Threads part 4 Slide # 8
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();
   }
}

Listen Here!S-nov11 4mins Doc 22, Threads part 4 Slide # 9
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();
   }
}

Listen Here!S-nov16 12secs Doc 22, Threads part 4 Slide # 10

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();
   }
}

Listen Here!S-nov11 11mins Doc 22, Threads part 4 Slide # 11

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();
   }
}


Listen Here!S-nov16 9mins Doc 22, Threads part 4 Slide # 12

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 );
      }
   }
}

Doc 22, Threads part 4 Slide # 13
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;
   }
}

Listen Here!S-nov16 8mins Doc 22, Threads part 4 Slide # 14
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 );
      }
   }
}

Listen Here!S-nov16 4mins Doc 22, Threads part 4 Slide # 15
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 );
      }
   }
}

Listen Here!S-nov16 2mins Doc 22, Threads part 4 Slide # 16
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;
   }
}

Listen Here!S-nov16 6mins Doc 22, Threads part 4 Slide # 17
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