Threads in Java

Active objects implement Java's runnable interface.

interface Runnable {
   public void run();
}

For example:

class Agent implements Runnable {
  State state, goal;
  void run() {
     while(state != goal) {
        state = update(state);
     }
  }
  State update(State state) { ... }
}

Threads are instances of Java's Thread class:

class Thread implements Runnable {
   private Runnable runner;
   public void start() {
      if (runner!= null) runner.run();
      else run();
   }
   public void run() { /* no-op */ } // overridable
   // etc.
}

If it has a runner, a thread executes its run function:

Thread thread = new Thread(new Agent());
thread.start();

Alternatively, programmers can directly extend Thread and override run:

class Agent extends Thread {
  State state, goal;
  void run() {
     while(state != goal) {
        update();
     }
  }
  void update() { ... }
}

This technique is a bit simpler because we don't need to create a separate thread:

Agent agent = new Agent();
agent.start();

However, Agent can't extend from more classes.

Example

In this example states are integers. The goal is to reach state 0 by repeatedly dividing by 3:

class Agent implements Runnable {

  private int state, goal;
  private String name;
 
  public Agent(String name, int state) {
     this.goal = 0;
     this.state = state;
     this.name = name;
  }
 
   public String toString() {
       return name + ".state = " + state;
    }

  public void run() {
     while(state != goal) {
        update();
        System.out.println(this.toString());
     }
  }

  public void update() {
     state = state/3;
  }

}

A manager manages a population of three agents:

public class Manager {
 
  private Agent[] agents;
 
  public Manager() {
     agents = new Agent[3];
     agents[0] = new Agent("Agent 1", 50);
     agents[1] = new Agent("Agent 2", -30);
     agents[2] = new Agent("Agent 3", 75);
  }
 
  public void run() {
     for(int i = 0; i < agents.length; i++) {
        Thread thread = new Thread(agents[i]);
        thread.start();
     }
     System.out.println("all done");
  }
 
  public static void main(String[] args) {
     Manager manager = new Manager();
     manager.run();
  }
}

Here's the output produced:

all done
Agent 1.state = 16
Agent 1.state = 5
Agent 1.state = 1
Agent 1.state = 0
Agent 3.state = 25
Agent 3.state = 8
Agent 3.state = 2
Agent 3.state = 0
Agent 2.state = -10
Agent 2.state = -3
Agent 2.state = -1
Agent 2.state = 0

Notice that each agent reached its goal, but they didn't take turns. That's because some operating systems don't interrupt threads unless the thread is suspended for some reason such as waiting for input. A cooperative thread suspends itself after each update by going to sleep for a few milliseconds:

Here's the run method of a cooperative agent:

public void run() {
  while(state != goal) {
     update();
     try {
        Thread.sleep(100); // be cooperative
     } catch (Exception e) {
           System.out.println(e);
     }

     System.out.println(this.toString());
  }
}

Here's the output produced:

all done
Agent 1.state = 16
Agent 3.state = 25
Agent 2.state = -10
Agent 2.state = -3
Agent 3.state = 8
Agent 1.state = 5
Agent 3.state = 2
Agent 1.state = 1
Agent 2.state = -1
Agent 2.state = 0
Agent 1.state = 0

Waiting for threads to die

Notice that the manager's run function terminates immediately after it starts its threads. (We can tell this because the "all done" message appears before the agents begin producing output.) This is a problem if the manager needs to do some work after the agents have stopped. For example, each agent might be computing the length of a route from San Jose to New York. After the lengths are computed, the manager must select the shortest one.

To solve this problem the Thread class has a join method. Calling this method causes the joiner to block until the thread finishes execution.

Unfortunately, our agents aren't threads. Instead, they run inside of threads (it would be different if Agent extended Thread). Somehow, the agent must catch the thread that it's running inside of. Fortunately, the thread class maintains a static reference to the currently executing thread. Here's how we can make use of this:

public class Agent implements Runnable {

  private Thread thread;
 
  public void join() throws InterruptedException {
     if (thread != null) thread.join();
  }


  public void run() {
     thread = Thread.currentThread(); // catch my thread
     while(!isStopped()) {
        update();
        if (state == goal) stop();
        else
           try {
             Thread.sleep(100);
           } catch (Exception e) {
             System.out.println(e);
           }
        System.out.println(this.toString());
     }
  }
  // etc.
}

After launching the threads, the manager joins the first thread, when it stops, it joins the second thread, etc. Joining a thread that's already stopped does nothing.

public class Manager {
 
  public void run() {
     for(int i = 0; i < agents.length; i++) {
        Thread thread = new Thread(agents[i]);
        thread.start();
     }
     // wait for agents to die
     for(int i = 0; i < agents.length; i++) {
        try {
           agents[i].join();
        } catch(InterruptedException ie) {
           System.err.println(ie.getMessage());
        } finally {
           System.out.println(agents[i].getName() + " has died");
        }
     }
     System.out.println("all done");
  }
  // etc.
}

Here's the output produced. Notice that the "all done" message appears at the end.

Agent 3.state = 25
Agent 2.state = -10
Agent 1.state = 16
Agent 3.state = 8
Agent 2.state = -3
Agent 1.state = 5
Agent 1.state = 1
Agent 1.state = 0
Agent 3.state = 2
Agent 2.state = -1
Agent 3.state = 0
Agent 1 has died
Agent 2.state = 0
Agent 2 has died
Agent 3 has died
all done