Creating Threads (Lea (2nd ed.) Ch. 4)

  • new Thread(aRunnable).start();
  • Generic architectures
    • Consider a web service which has actor style "daemon" form

for (;;) {
  accept request
  create task
}

    • which then creates "handler" tasks which handle each incoming request


class WebService implements Runnable {
  static final int PORT = 1040;
  Handler handler = new Handler();

  public void run() {
    try {
      ServerSocket socket = new ServerSocket(PORT);
      for (;;) {
        final Socket connection = socket.accept();
        new Thread ( new Runnable() {
          public void run() {
            handler.process(connection);
          }}).start();
      }
    }
    catch(Exception e) {} //die
  }
  public static void main(String[] args) {
    new Thread(new WebService()).start();
  }
}

class Handler {
  void process(Socket s) {
    DataInputStream in = null;
    DataOutputStream out = null'
    try {
      in = new DataInputStream(s.getInputStream());
      out = new DataOutputStream(s.getOutputStream());
      int request = in.readInt();  //return negation to client
      int result = -request;
      out.writeInt(result);
    }
    catch(IOException ex) {}  //fall through
    finally {                              //clean up
      try{if(in != null) in.close(); }
      catch (IOException ignore) {}
      try {if(out != null) out.close(); }
      catch (IOException ignore) {}
      try {s.close(); }
      catch (IOException ignore) {}
    }
  }
}

    • Worker Threads
      • lightweight executable frameworks can be constructed in many ways, but all stem from the basic idea of using one thread to execute many unrelated tasks
      • each worker continually accepts new Runnable commands from hosts and holds them in some kind of channel until they can be run
      • lightweight executable frameworks can improve the structure of some task-based concurrent programs, by allowing you to package many smaller, logically asynchronous units of execution as tasks without having to worry much about performance consequences: entering a Runnable into a queue is likely to be faster than creating a new Thread object.


class PlainWorkerPool implements Executor {
  protected final Channel workQueue;
  public void execute(Runnable r) {
    try {
      workQueue.put(r);
    }
    catch (InterruptedException ie) { //postpone response
      Thread.currentThread();.interrupt();
    }
  }
  public PlainWorkerPool(Channel ch, int nworkers) {
    workQueue = ch;
    for (int i= 0; i< nworkers; ++i) activate();
  }
  protected void activate() {
    Runnable runLoop = new Runnable() {
      public void run() {
        try {
          for (;;) {
            Runnable r = (Runnable)(workQueue.take());
            r.run();
          }
        }
        catch (InterruptedException ie) {} //die
      }
    };
    new Thread(runLoop).start();
  }
}
 

      • Design Choices
        • most worker threads must be treated anonymously; they all do the same thing
        • Runnable tasks that are sitting in queues do not run; all dependencies between tasks must be enforced by the channel; you need to create custom queues which enforce the dependencies between tasks
        • You can adapt to saturation by increasing the pool size, building "back pressure" notification schemes, discarding new requests, dropping old (assumedly outdated) requests, or blocking until new space becomes available
        • In thread management, you can create all threads at initialization time or build them upon demand; you can also allow threads to time-out and terminate themselves after being idle for some period of time (this will recover resources)
        • Cancellation: distinguish between cancellation of a task from cancellation of a worker; 1) upon interruption allow the current work thread to die (you may have to replace it later; 2) provide a shutdown method in the worker thread class;
      • Event queues (for things like java.awt and javax.swing packages)
        • the queue holds instances of EventObject that must be dispatched (by a single thread), normally to listener objects defined by the application.
        • use of a single thread dispatcher allows you to prioritize the event queue to optimize handling of events; if you force all methods operating on specific objects be invoked only by issuing events onto the queue, you essentially achieve thread confinement.