Java & Threads

Threads are essentially subprocesses1: informally, we can think of them as tasks that run "simultaneously" within a program. For example, a web server application may have a number of threads running at a given time, each responding to a different web page request. A graphics rendering application may have different threads running, each rendering a different portion of an image. A strategy game might have multiple threads running, each exploring different potential moves for an AI player to make.

When multiple threads are in process, the computer's operating system and processor(s) between them determine how to actually distribute the threads among the available CPU resources. If precisely two threads are running and there are two CPU cores available, then in principle, the threads can literally run simultaneously, one on each core. But the reality is usually muchg more complex:

This complex process of allocating threads to available resources is generally termed thread scheduling.

Allocating threads to available CPU resources is complex in part because of the complexity of modern computer architectures, which are increasingly designed around allowing mutliple processes to run in parallel. But the converse is therefore true: if your program doesn't make use of threads in some way, it is likely that you will not be able to make maximum use of the available computing resources.

Java threading APIs

Like modern programming languages generally, Java has a range of APIs available for managing threads within your application:

Getting started with the Java threading API

It's easier to illustrate what a thread is by diving straight in and seeing some code. We're going to write a program that "splits itself" into two simultaneous tasks. One task is to print Hello, world! every second. The other task is to print Goodbye, cruel world! every two seconds. OK, it's a silly example.

For them to run simultaneously, each of these two tasks will run in a separate thread. To define a "task", we create an instance of Runnable. Then we will wrap each of these Runnables around a Thread object.

Runnable

A Runnable object defines an actual task that is to be executed. It doesn't define how it is to be executed (serial, two at a time, three at a time etc), but just what. We can define a Runnable as follows:

Runnable r = new Runnable() {
  public void run() {
    ... code to be executed ...
  }
};

Runnable is actually an interface, with the single run() method that we must provide. In our case, we want the Runnable.run() methods of our two tasks to print a message periodically. So here is what the code could look like:

Runnable r1 = new Runnable() {
  public void run() {
    try {
      while (true) {
        System.out.println("Hello, world!");
        Thread.sleep(1000L);
      }
    } catch (InterruptedException iex) {}
  }
};
Runnable r2 = new Runnable() {
  public void run() {
    try {
      while (true) {
        System.out.println("Goodbye, " +
		"cruel world!");
        Thread.sleep(2000L);
      }
    } catch (InterruptedException iex) {}
  }
};

For now, we'll gloss over a couple of issues, such as how the task ever stops. As you've probably gathered, the Thread.sleep() method essentially "pauses" for the given number of milliseconds, but could get "interrupted", hence the need to catch InterruptedException. We'll come back to this in more detail in the section on Thread interruption and InterruptedException. The most important point for now is that with the Runnable() interface, we're just defining what the two tasks are. We haven't actually set them running yet. And that's where the Thread class comes in...

Thread

A Java Thread object wraps around an actual thread of execution. It effectively defines how the task is to be executed— namely, at the same time as other threads2. To run the above two tasks simultaneously, we create a Thread object for each Runnable, then call the start() method on each Thread:

Thread thr1 = new Thread(r1);
Thread thr2 = new Thread(r2);
thr1.start();
thr2.start();

When we call start(), a new thread is spawned, which will begin executing the task that was assigned to the Thread object at some time in the near future. Meanwhile, control returns to the caller of start(), and we can start the second thread. Once that starts, we'll actually have at least three threads now running in parallel: the two we've just started, plus the "main" thread from which we created and started the two others. (In reality, the JVM will tend to have a few extra threads running for "housekeeping" tasks such as garbage collection, although they're essentially outside of our program's control.)

Next: threading topics

On the next page, we look at the following Java threading topics:


1. A process is essentially a "heavy" unit of multitasking: as an approximation, think of it as an "application" (though it can include 'background' or 'system' processes such a your battery monitor or file indexer). A thread, on the other hand, is a more "lightweight" unit. Different processes generally have certain resources allocated independently of one another (such as address space, file handle allocations), whereas a threads generally share the resources of their parent process.
2. We'll see later that the Thread object also encapsulates other details, such as a thread's priority.