Threading issues

How to return data from a running thread?

By Margrit Höhme, java@mhoehme.de, 2002

First Draft (5 Nov, 2002)


This essay points out three solutions to the problem of sending data from one thread to another. For each solution a "cookbook" example code is provided to illustrate the respective solution.

Sending data from one thread to another is very similar to sending data from one object to another. However, the main difference is to take care that data is not corrupted due to concurrent access, that is, several threads accessing the same data. To avoid concurrent access, the methods accessed by the threads involved in the data exchange have to be synchronized.

Multi-threading is a non-trivial task. Simply because Java provides some simple means of governming multiple threads, that does not mean that multi-threading has become simple!

  • Callback methods
  • Events
  • Cubby Hole
  • Discussion
  • References

  • Callback methods

    The callback method is a fairly simple way to communicate results between objects. The callback strategy can also be used to communicate results between threads.

    Participants

    Cookbook code

    /**
     * The interface defines the callback method(s).
     */
    public interface MyInterface
    {
       /**
        * Set the specified data.
        * @param data the data to be returned to the first thread
        */
       public void setResult (Object result);
    }
    
    
    public class MyClass implements MyInterface
    {
       /* 
        * Attention: This method is accessed by the thread which
        * executes MyThread!
        */
       public synchronized void setResult (Object result)
       {
          // do something with the result
       }
       public void createThread ()
       {
          MyThread t = new MyThread();
          t.setReceiver (this);
          t.start();
       }
    }
    
    
    public class MyThread extends Thread
    {
       private MyInterface receiver;
       public void setReceiver (MyInterface receiver)
       {
           this.receiver = receiver;
       }
       public void run ()
       {
          Object result = compute ();
          receiver.setResult (result); // callback
       }
       private Object compute ()
       {
          Object result = null;
          // do some heavy computing...
          return result;
       }
    }
    

    Remark: It is not always necessary to define a separate interface. The MyClass object itself can be passed as a parameter to the callback method:

        public void setReceiver (MyClass receiver)
        {
            this.receiver = receiver;
        }
    
    Using an interface has the advantage that the interface encapsulates the callback. This means that the receiver is less tightly coupled to the class that produces the result.


    Events

    Events are a well-known pattern, which is also known as Publisher/Subscriber. The main feature of the event strategy is that the result is wrapped into a subclass of java.util.EventObject. Events are much like a variation of the callback strategy. However, events can be multicasted to any number of interested classes.

    Participants

    Cookbook code

    public class MyEvent extends java.util.EventObject
    {
       private Object result;
       public MyEvent (Object o)
       {
          super (o);
       }
       public MyEvent (Object o, Object result)
       {
          super (o);
          this.result = result;
       }
       public Object getResult ()
       {
          return result;
       }
    }
    
    
    public interface MyListener extends java.util.EventListener
    {
       public void processEvent (MyEvent e);
    }
    
    
    public class MyClass implements MyListener
    {
       /**
         * Implementation of the MyListener interface.
         * Because this method is executed by the other thread,
         * it must be synchronized to avoid possible corruption
         * of the data.
         */
       public synchronized void processEvent (MyEvent e)
       {
          Object result = e.getResult();
          // do something with the result
       }
       public void createThread ()
       {
          MyThread t = new MyThread();
          t.addMyListener (this);
          t.start();
       }
    }
    
    
    public class MyThread extends Thread
    {
       private java.util.Vector listeners;
    
       public void addMyListener (MyListener l)
       {
          if (listeners == null)
          {
             listeners = new Vector();
          }
          listeners.add (l);
       }
       public void removeMyListener (MyListener l)
       {
          if (listeners != null)
          {
             listeners.remove (l);
          }
       }
       /**
        * Multicast the event to all registered listeners.
        */
       private void fireMyEvent (Object result)
       {
          MyEvent e = new MyEvent (this, result);
    
          Vector list = null;
          synchronized (this) 
          {
              list = (Vector) listeners.clone();
          }
    
          int size = list.size();
          for (int i = 0; i < size; i++)
          {
              MyListener l = (MyListener) list.elementAt (i);
              l.processEvent (e);
          }  
       }
       public void run ()
       {
          Object result = compute ();
          fireMyEvent (result);
       }
       private Object compute ()
       {
          Object result = null;
          // compute result
          return result;
       }
    }
    

    Cubby Hole

    The third method is the "Cubby Hole", which is described in the Java Tutorial.

    The Cubby Hole lends itself to situations where one thread depends on the data produced by another thread. The main features of the Cubby Hole is that the receiving thread is blocked until data is available. The cubby hole is a mutex (from "mutually exclusive"): Only one thread at a time may have access to it.

    The producing thread deposits some data in the cubby hole. The receiving thread picks up the data from the cubby hole. When the producer wants to deposit another chunk of data, it must wait until the cubby hole is "empty" again. The cubby hole may also implement a queue for depositing the data and picking up again.

    Participants

    Cookbook code

    This example has been adapted from the Java Tutorial. For an excellent discussion, please refer to the Java Tutorial.

    public class CubbyHole
    {
        private boolean available;
        private Object result;
        public sychronized Object getResult ()
        {
            while (available == false) {
                try 
                {
                    wait();
                } 
                catch (InterruptedException e) 
                {
                    // ignore
                }
            }
            available = false;
            notifyAll();
            return result;
        }
        public synchronized void setResult (Object result)
        {
            while (available == true) 
            {
                try 
                {
                    wait();
                } 
                catch (InterruptedException e) 
                {
                    // ignore
                }
            }
            this.result = result;
            available = true;
            notifyAll();
        }
    }
    
    
    public class MyClass
    {
       private CubbyHole cubbyHole;
       public void createThread ()
       {
          cubbyHole = new CubbyHole();
          MyThread t = new MyThread();
          t.setCubbyHole (cubbyHole);
          t.start();
       }
       public void processResult ()
       {
          Object result = cubbyHole.getResult();
          // do something with the result
       }
    }
    
    
    public class MyThread extends Thread
    {
       private CubbyHole cubbyHole;
       public void setCubbyHole (CubbyHole cubbyHole)
       {
          this.cubbyHole = cubbyHole;
       }
       public void run ()
       {
          Object result = compute();
          cubbyHole.setObject (result);
       }
       private Object compute ()
       {
          Object result = null;
          // compute result...
          return result;
       }
    }
    

    Discussion

    This essay has described three ways to send data from one thread to another.

    Callback methods are a simple way for objects to communicate with each other. Callback methods work well for a single receiver, which is known at compile time. Doug Lea discusses the callback strategy extensively.

    Events are a common way for asynchronous communication between threads. Events are multicasted to any number of interested classes. Therefore events are a preferred strategy if the number of classes interested in the result cannot be known in advance. Moreover, the number of receivers may even vary at runtime. Events allow a very loose coupling.

    CubbyHoles can be used if one thread depends on the data of another thread. Both threads, the producer and the receiver, are completely decoupled and only communicate via the "cubby hole" object. However, the producing and the receiving threads act very closely together since they block each other on the "cubby hole" mutex.

    This essay can only outline strategies to send data from one thread to another. For an in-depth discussion of threads, refer to Doug Lea's book Concurrent Programming, which is a standard book on threading issues.


    References


    Margrit Höhme, java@mhoehme.de, 2002.
    Last Modified: