I learned computer programming on an IBM 1620 (so named because it came over on the Mayflower) (not really), with 20,000 decimal digits (memory positions holding one of 10 possible values, not bytes holding one of 256 possible values) of memory (expandable to 60,000), and attached to a card reader/punch, a hard disk that resembled a stack of LPs and could hold one million (not 1024 x 1024) decimal digits (again, not bytes), and a typewriter.

The point of this anecdote is what a program on this computer–and it could only run one program at a time–did whenever it had to read or punch a card, or accept or print text via the typewriter, or read or write from the disk: it stopped dead in its tracks and waited for the operation to finish.

Meanwhile, in another part of town… the local university’s computing center was running an IBM System/360. It too used disks, punchcards, and a typewriter, but whenever a program communicated with one of these, it could do other things while the input or output operation continued. And if it chose to or needed to wait, some other program running in the system–and there could be many–could have a turn.

Your PC, Mac, or smart phone is, of course, more like the System/360, in that it can juggle multiple processes at once. We say each process runs in its own Thread, and a thread can spawn threads of its own that run concurrently with, and independently of, the original thread. This lesson deals with how to do that.

Why use threads? Usually it’s to let a main process continue while some other complicated or I/O bound process can work in the background. (A process is I/O bound if it spends most of its time reading and writing.) If you’ve ever run an interactive program that performed some long-running task but also offered you a “Cancel” button to stop it if it was taking too long, the long-running task was running in one thread and the button was managed by another. Clicking the button would cause its thread to signal the other thread that it should terminate.

Or say you have a process that takes in inputs and creates outputs, but spends much of its time waiting for the outputs to be written. You could create a separate thread for each input record, and while one of these threads waits for its output to be written, another thread could be busy preparing its outputs.

How to Define a Thread

Java has two ways to define and initiate a Thread:

  • As a subclass of Thread;
  • As a class that implements the Runnable interface.

In both cases, the class must contain a method without arguments named run().

When subclassing Thread, the thread is defined, created, and started like so:

public class MyThead extends Thread {
    public void run() {
       ...
    }
}
...
private void createTheThread() {
    MyThread theNewThread = new MyThread(...);
    theNewThread.start();
}

By contrast, here’s how to create and start a thread by implementing Runnable:

public class MyThead implements Runnable {
    public void run() {
       ...
    }
}
...
private void createTheThread() {
    MyThread theProcess = new MyThread(...);
    Thread theNewThread = new Thread(theProcess);
    theNewThread.start();
}

The Runnable interface–which is itself implemented by Thread–contains a single method: run(), which accepts no arguments.

Notice how the two classes to be run in separate threads are virtually identical: the only difference between them is the use of either extends Thread or implements Runnable. But the ways of creating and starting them differ. When extending Thread, all you need to do is create an instance of the class. When implementing Runnable, you create the instance and then pass it to a constructor of Thread.

In both cases, you can–and likely will–pass arguments to the constructor. And in both cases, you get the thread going by calling its start() method, which ultimately calls the Runnable’s run() method.

Coordinating Main and Child Threads

A main thread–one that starts a child thread–has an interest in knowing the status of that child thread, and possibly may need to wait for its completion or interrupt it. Here’s how that works.

A thread is said to be alive once start() has been called upon it, until its run() method has run to completion–at which point we say it has died.

Here are methods used to coordinate the activity of a child thread.

Thread methodFunction
boolean isAlive()Returns true if the thread instance is still alive.
void join()
void join(long millis)
void join(long millis, in nanos)
Waits for the thread instance to die. In the first overload, or if millis (and nanos) is zero, the wait is indefinite. In the latter two, if the specified time in milliseconds (plus nanoseconds, if specified) elapses, execution proceeds; it’s up to the caller to check whether the subject thread has actually died.

If another thread calls or has called interrupt() on the thread waiting for a join, the waiting thread throws an InterruptedException and the interrupted status is cleared.
void interrupt()Interrupts the thread instance–meaning it sets the interrupted status to true, not that the thread instance immediately stops. Rather, the thread instance needs to check if it’s been interrupted via the interrupted() or isInterrupted() method.

If the thread instance is blocked because it’s waiting on a wait(), join(), or sleep() call, the interrupted status is cleared and it receives an InterruptedException.
boolean interrupted()
boolean isInterrupted()
Called by a child thread. In either case, returns true if the thread’s interrupted status is true. The difference is that interrupted() clears the interrupted status, while isInterrupted() has no such effect.
static void sleep()
static void sleep(long millis)
static void sleep(long millis, in nanos)
Pauses the current thread for the specified number of milliseconds (plus nanoseconds).

If another thread calls or has called interrupt() on the sleeping thread, the sleeping thread throws an InterruptedException and the interrupted status is cleared.

ThreadLocal and InheritableThreadLocal

Suppose you have a class (or set of classes) whose instances (or static methods) share one or more static objects. For example, you might work with a database with a single Connection and a number of PreparedStatements. These things are static because they need to be initialized just once, and then shared with many instances of the containing class and/or other classes.

Now, if you want to take advantage of multithreading, you wouldn’t want to share your one Connection with all the threads. Things will be much less complicated and much more efficient if each thread has its own Connection. But how can you keep the Connection static and still have a separate Connection for each thread?

That’s where ThreadLocal comes in. ThreadLocal is like a Map, where the key is a Thread and the value is some other object. The difference between ThreadLocal and a Map, though, is that when a thread dies, any object added to a ThreadLocal is deallocated automatically.

Here’s an example of using a ThreadLocal for a Connection object:

public static ThreadLocal<Connection> conn = new ThreadLocal<Connection>();
...
   Connection c = ConnectionManager.getConnection();
   conn.set(c);
   ...
   Connection c = conn.get();
   ...
   conn.remove();

It couldn’t be simpler. You allocate the ThreadLocal (notice that it uses generics), use the set() method to set its value for the current thread, use get() to retrieve that value, and call remove() when you want to get rid of the value. If the value hasn’t yet been set, or has been removed, get() returns the ThreadLocal’s initial value–usually null, but you can override ThreadLocal’s initialValue() method to provide a different value; see the API documentation for details.

And InheritableThreadLocal? That’s the same as a ThreadLocal, only the value set there by a given thread is inherited by threads created by that thread.

Waiting, Notification, and Synchronization

Suppose you’ve started a number of threads and have reached a point where you need one or more of them to have finished before you can proceed.

You could code something like this:

MyThread t = null;
if (!listOfThreads.isEmpty()) {
    while (t == null) {
        for (MyThread temp : listOfThreads) {
            if (!temp.isAlive()) {
                t = temp;
                break;
            }
        }
    }
}

… but you’d be tying up the CPU; in fact, none of your threads may ever finish because they can’t get a chance to run.

You could follow the for loop above with a call to sleep(), but then when one of your child threads finishes you might still be sleeping when you could be working.

You could call join() on the first child thread in the list, but if some other thread finishes first, you again might be sleeping when you could be capitalizing on that completed thread. You could put a time limit on the join() call, moving on to the next child if the child is still alive–but again, you might be waiting when you could be working.

To make the most of your application’s time, Java provides waiting, notification, and synchronization.

Synchronization

Consider two cars approaching an intersection from different streets. There is a resource they both want: the right of way. Synchronization is the process of making them take turns. Car A can acquire a lock on that resource, and until it passes through the intersection–releases the lock–car B must wait. And synchronization works the same in Java; the resource is an object; a process holding a lock on that object has the right of access to that object, and other processes must wait for the lock to be released.

… On Methods

It’s common to have multiple threads share an object. And you can get into trouble if they don’t take turns using that object. Consider the following example, taken from the Oracle web site:

class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}

Suppose you have two threads, A and B, sharing a Counter object. Thread A calls increment() around the same time B calls decrement(), and there’s no guarantee one call will finish before the other one begins. The following is a distinct possibility:

  • A retrieves the value of c, which is 0.
  • B retrieves the value of c, which is 0.
  • A increments c to 1 and saves it. value() returns 1.
  • B decrements c to -1 and saves it. value() returns -1.

… and at the end, c has the value -1. On the other hand, if thread’s call ran completely before the other began, c would be zero. Which would be the end result cannot be predicted.

Synchronization solves this dilemma. We can add the synchronized keyword to each method in Counter:

class Counter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

Once a thread enters a synchronized instance method, it hold a lock on the object instance until the thread exits the method. No other thread can enter a synchronized instance method on that object as long as the lock persists. So if thread B calls decrement() or value() while thread A is executing increment(), it will wait until A’s call to increment() is done.

The same applies at the class level for static methods: if one thread calls a synchronized static method, calls from other threads to static methods of the same class wait until the first call is complete. In this case, the lock is held on the class.

… On Code Blocks

Frequently, you don’t really need to lock an entire method–or set of methods. You just need to let threads have exclusive access a few lines of code. Consider this example from javaTpoint:

    void printTable(int n) {
        synchronized (this) {
            for (int i = 1; i <= 5; i++) {
                System.out.println(n * i);
                try {
                    Thread.sleep(400);
                } catch (Exception e) {
                    System.out.println(e);
                }
            }
        }
    }

If two threads entered printTable() concurrently without the synchronization, the resulting printout would be a mess. With synchronization–in this case, on the instance of the class itself–the problem is solved. Note that synchronization on a code block only restricts access to that block–not to other methods or other blocks.

… On Objects

The object on which synchronization is done is called a monitor variable. When we synchronize on methods, the monitor variable is an object instance (as in the previous example) or a class. But it can also be an instance of a field. Strictly speaking, it doesn’t have to be a field that serves any other purpose. But more typically, it’s a field that threads will tamper with–and we don’t want any two threads accessing that field until the tampering is done.

There are entire classes, and implementations of interfaces, which are not thread-safe, meaning they lack any mechanism to keep multiple threads from reading or changing their contents concurrently. Two examples are List and SimpleDateFormat. It’s always a good policy to check the API documentation on a class before assuming that it doesn’t require synchronization.

Waiting and Notification

What if a method having a lock on a monitor variable reaches a point where it can safely release that lock? Java has a mechanism that lets other threads wait for the lock to be released, and for the locking thread to release it, without waiting for the locking thread to finish a method or code block.

For this discussion, you’ll want to download Lesson-26-Example-01 and import it to your workspace.

Remember how we downloaded a page from a web site and passed it to a browser in Lesson 25? We’re now going to do the same thing with three web sites. And we’ll use threads to let the downloads happen concurrently.

Class PageDownload

PageDownload is the class that will run in a child thread.

public class PageDownload implements Runnable {
    // address is the address of the web page we want to open.
    // exception is the Throwable thrown during execution of the
    // thread, if any.
    // sequence is the sequence in which this thread was started.
    private String address = null;
    private Throwable exception = null;
    private int sequence = 0;

    private List<PageDownload> completedDownloadList;

completedDownloadList will be our monitor variable. It’s a List that resides in class PageDownloadDriver, the class that will create and start instances of this class. When a PageDownload instance completes execution, it adds itself to this completedDownloadList.

    public void run() {
        File htmlFile = null;

        try {
            htmlFile = File.createTempFile(...);
            processDownload(htmlFile);
        } catch (Throwable e) {
            exception = e;
        } finally {
            if (htmlFile.exists()) {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                }
                try {
                    htmlFile.delete();
                } catch (SecurityException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
    }

When the driver issues start() on the PageDownload thread, the JVM calls the run() method. It creates an output file to receive the download and passes it to method processDownload(). The finally block ensures that whatever the outcome of processDownload(), if the file actually exists, we’ll delete it after first sleeping a second and a half to let the web browser finish loading it. We don’t care if the thread is interrupted during sleep(), and there’s nothing we can do about a SecurityException on a file we actually created, so exception handling in the finally block is minimal.

For its part, processDownload() is similar to what we saw in Lesson 25: it downloads data from a file on the Internet, writes it to a file, and hands that file over to a web browser. The tricky part comes when all that is done:

    private void processDownload(File htmlFile) {
        try {
...
            // Add this instance to completedDownloadList and issue
            // notifyAll.
            synchronized (completedDownloadList) {
                completedDownloadList.add(this);
                completedDownloadList.notifyAll();
            }

        } catch (Throwable e) {
            this.exception = e;
        }

Once the downloaded file has been given to the web browser, our work is done. We want to add this instance of PageDownload to completedDownloadList. But we have to be careful: we share completedDownloadList with another thread–the one that created this one–as well as the other PageDownload threads. So we synchronize on completedDownloadList to lock it, add this instance of PageDownload to it, and then call notifyAll() to wake up any thread waiting on that lock. After calling notifyAll(), we leave the synchronized block, which releases the lock and lets the awakened threads proceed.

notifyAll() wakes up all threads waiting on the monitor variable. notify() wakes up only one–which one cannot be predicted. notifyAll() is preferred.

Class PageDownloadDriver

public class PageDownloadDriver {
    private static final String DECLARATION_URL = "https://www.archives.gov/founding-docs/declaration-transcript";
    private static final String YAHOO_URL = "https://www.yahoo.com";
    private static final String AOL_URL = "https://www.aol.com";

    private static final String[] ADDRESS_ARRAY = //
            { AOL_URL //
                    , YAHOO_URL //
                    , DECLARATION_URL };

    // List of active PageDownload instances.
    private List<PageDownload> liveDownloadList = new ArrayList<PageDownload>();
    
    // List of completed PageDownload instances, shared with PageDownload.
    private List<PageDownload> completedDownloadList = new ArrayList<PageDownload>();

Our driver class starts with an array of URLs from which we’ll be downloading. liveDownloadList is a list of PageDownload instances whose threads are still running. completedDownloadList is where each PageDownload instance adds itself when it’s finished.

    private void execute() {
        int sequence = 0;
        for (String address : ADDRESS_ARRAY) {
            PageDownload download = new PageDownload(address, ++sequence, completedDownloadList);
            liveDownloadList.add(download);
        }

        // Start threads for each instance in liveDownloadList.
        for (PageDownload download : liveDownloadList) {
            new Thread(download).start();
        }

execute() is the only method in PageDownloadDriver. It begins by creating an instance of PageDownload for each of our URLs, passing completedDownloadList and adding them to liveDownloadList. Then it starts a Thread for each of them.

        // Repeat this loop until there are no more instances
        // in liveDownloadList.
        while (!liveDownloadList.isEmpty()) {
            // Wait for one or more of the threads to notify, or
            // until 7.5 secs. have elapsed.
            try {
                synchronized (completedDownloadList) {
                    if (completedDownloadList.isEmpty()) {
                        completedDownloadList.wait(7500);
                    }
                }
            } catch (InterruptedException e) {
            }

Now we enter a loop that will repeat as long as there are instances in liveDownloadList. First, we synchronize on completedDownloadList so we can examine it without interference from other threads–remember that PageDownload adds items to it in a separate thread. If it’s empty, we’ll wait until one or more PageDownload threads has a chance to add to it.

The wait() method can’t be used on an object unless the caller owns the lock on that object. The synchronized header has accomplished that. For its part, wait() releases the lock so other threads can use the object; we’d be waiting forever if they couldn’t.

            // Copy instances from completedDownloadList to a temporary list,
            // then clear completedDownloadList.
            List<PageDownload> tempList;
            synchronized (completedDownloadList) {
                tempList = new ArrayList<PageDownload>(completedDownloadList);
                completedDownloadList.clear();
            }

Our next step is to lock completedDownloadList again and spirit its contents away to a temporary list that PageDownload can’t reach. Notice that we don’t care if any of the PageDownload threads has snuck in and added to completedDownloadList between the last lock and this one.

            // Report on the status of the completed downloads.
            for (PageDownload instance : tempList) {
                System.out.println("Thread for URL " + instance.getAddress() + " sequence " + instance.getSequence()
                        + " has completed.");
                if (instance.getException() != null) {
                    System.out.println("Exception occured in thread for URL: " + instance.getAddress());
                    instance.getException().printStackTrace();
                }
            }

And now we report on the outcome of whatever completed downloads were available. Note that although the PageDownload threads are dead, the PageDownload instances still exist and you can call methods within them–such as getSequence(), getAddress(), and getException().

            // Remove the completed downloads from liveDownloadList.
            // If this empties liveDownloadList, we're done.
            liveDownloadList.removeAll(tempList);
        }

Finally, we remove all the completed PageDownload instances from liveDownloadList, and the while loop begins anew.

So try running PageDownloadDriver in Lesson-26-Example-01. You’ll find that although the first PageDownload goes to AOL, the second to Yahoo, and the third to a transcript of the Declaration of Independence, the process of reading them doesn’t finish in that order. In fact, from one execution to the next, the order may change. Such is the way of threads.

What You Need to Know

  • Threads allow multiple processes to run concurrently, which increases throughput.
  • Classes that run in child threads can be declared in one of two ways:
    • As a subclass of Thread;
    • As an implementation of Runnable (preferred).
  • The join() method allows a thread to pause until another thread finishes running.
  • Threads frequently share resources. Synchronization is available to prevent conflicts and race conditions.
  • Synchronization is always done on some object, called the monitor variable. It can be done on a class, an instance, or a variable.
  • Synchronization can control access to sets of static methods, sets of instance methods, code blocks, or variables.
  • Synchronization establishes a lock on the monitor variable, which is released when execution leaves the scope of the synchronization or by a call to wait().
  • The notify() and notifyAll() methods let a thread signal the JVM that other threads, waiting on a resource via the wait() method, can proceed.

Next: Graphics and Printing