free web hosting | website hosting | Web Hosting | Free Website Submission | shopping cart | php hosting
affordable web hosting | Pets | web page hosting | web hosting | website hosting | web hosting service | web hosting | best web hosting
Free webhosts Streetview photos

What Is a Thread?

All programmers are familiar with writing sequential programs. You've probably written a program that displays "Hello World!" or sorts a list of names or computes a list of prime numbers. These are sequential programs. That is, each has a beginning, an execution sequence, and an end. At any given time during the runtime of the program, there is a single point of execution.

A thread is similar to the sequential programs described previously. A single thread also has a beginning, a sequence, and an end and at any given time during the runtime of the thread, there is a single point of execution. However, a thread itself is not a program; it cannot run on its own. Rather, it runs within a program. The following figure shows this relationship.

 


Definition:  A thread is a single sequential flow of control within a program.

There is nothing new in the concept of a single thread. The real hoopla surrounding threads is not about a single sequential thread. Rather, it's about the use of multiple threads in a single program, running at the same time and performing different tasks. This is illustrated by the following figure:

 

The HotJava Web browser is an example of a multithreaded application. Within the HotJava browser you can scroll a page while it's downloading an applet or image, play animation and sound concurrently, print a page in the background while you download a new page, or watch three sorting algorithms race to the finish. You are used to life operating in a concurrent fashion...so why not your browser?

Some texts use the name lightweight process instead of thread. A thread is similar to a real process in that a thread and a running program are both a single sequential flow of control. However, a thread is considered lightweight because it runs within the context of a full-blown program and takes advantage of the resources allocated for that program and the program's environment.

As a sequential flow of control, a thread must carve out some of its own resources within a running program. (It must have its own execution stack and program counter for example.) The code running within the thread works only within that context. Thus, some other texts use execution context as a synonym for thread.

Customizing a Thread's run Method

The run method gives a thread something to do. Its code implements the thread's running behavior. It can do anything that can be encoded in Java statements: compute a list of prime's, sort some data, perform some animation.

The Thread class implements a generic thread that, by default, does nothing. That is, the implementation of its run method is empty. This is not particularly useful, so the Thread class defines API that lets a Runnable object provide a more interesting run method for a thread.

There are two techniques for providing a run method for a thread:

Subclassing Thread and Overriding run

The first way to customize what a thread does when it is running is to subclass Thread (itself a Runnable object) and override its empty run method so that it does something. Let's look at the SimpleThread class, the first of two classes in this example, which does just that:
public class SimpleThread extends Thread {
    public SimpleThread(String str) {
        super(str);
    }
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i + " " + getName());
            try {
                sleep((long)(Math.random() * 1000));
            } catch (InterruptedException e) {}
        }
        System.out.println("DONE! " + getName());
    }
}
The first method in the SimpleThread class is a constructor that takes a String as its only argument. This constructor is implemented by calling a superclass constructor and is interesting to us only because it sets the Thread's name, which is used later in the program.

The next method in the SimpleThread class is the run method. The run method is the heart of any Thread and where the action of the Thread takes place. The run method of the SimpleThread class contains a for loop that iterates ten times. In each iteration the method displays the iteration number and the name of the Thread, then sleeps for a random interval of up to 1 second. After the loop has finished, the run method prints DONE! along with the name of the thread. That's it for the SimpleThread class.

The TwoThreadsDemo class provides a main method that creates two SimpleThread threads: one is named "Jamaica" and the other is named "Fiji". (If you can't decide on where to go for vacation you can use this program to help you decide--go to the island whose thread prints "DONE!" first.)

public class TwoThreadsDemo {
    public static void main (String[] args) {
        new SimpleThread("Jamaica").start();
        new SimpleThread("Fiji").start();
    }
}
The main method also starts each thread immediately following its construction by calling the start method.  You should see output similar to the following:
0 Jamaica
0 Fiji
1 Fiji
1 Jamaica
2 Jamaica
2 Fiji
3 Fiji
3 Jamaica
4 Jamaica
4 Fiji
5 Jamaica
5 Fiji
6 Fiji
6 Jamaica
7 Jamaica
7 Fiji
8 Fiji
9 Fiji
8 Jamaica
DONE! Fiji
9 Jamaica
DONE! Jamaica
(Looks like I'm going to Fiji!!) Notice how the output from each thread is intermingled with the output from the other. This is because both SimpleThread threads are running concurrently. Thus, both run methods are running at the same time and each thread is displaying its output at the same time as the other.

 

Implementing the Runnable Interface

The Clock applet uses a different technique than SimpleThread for providing the run method for its thread. Instead of subclassing Thread, Clock implements the Runnable interface (and therefore implements the run method defined in it). Clock then creates a thread and provides itself as an argument to the Thread's constructor. When created in this way, the Thread gets its run method from the object passed into the constructor. The code that accomplishes this is shown in bold here:

import java.awt.Graphics;
import java.util.*;
import java.text.DateFormat;
import java.applet.Applet;

public class Clock extends Applet implements Runnable {
    private Thread clockThread = null;
    public void start() {
        if (clockThread == null) {
            clockThread = new Thread(this, "Clock");
            clockThread.start();
        }
    }
    public void run() {
        Thread myThread = Thread.currentThread();
        while (clockThread == myThread) {
            repaint();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e){
            // the VM doesn't want us to sleep anymore,
            // so get back to work
            }
        }
    }
    public void paint(Graphics g) {
        // get the time and convert it to a date
        Calendar cal = Calendar.getInstance();
        Date date = cal.getTime();
        // format it and display it
        DateFormat dateFormatter = DateFormat.getTimeInstance();
        g.drawString(dateFormatter.format(date), 5, 10);
    }
    // overrides Applet's stop method, not Thread's
    public void stop() {
        clockThread = null;
    }
}

The Clock applet's run method loops until the browser asks it to stop. During each iteration of the loop, the clock repaints its display. The paint method figures out what time it is, formats it in a localized way, and displays it. You'll see more of the Clock applet in The Life Cycle of a Thread which uses it to teach you about the life of a thread.

 

Deciding to Use the Runnable Interface

You have now seen two ways to provide the run method for a Java thread:
  1. Subclass the Thread class defined in the java.lang package and override the run method.

     


    Example:  See the SimpleThread class described in Subclassing Thread and Overriding run.
  2. Provide a class that implements the Runnable interface (also defined in the java.lang package) and therefore implements the run method. In this case, a Runnable object provides the run method to the thread.

     


    Example:  See the Clock applet just shown.
There are good reasons for choosing either of these options over the other. However, for most cases, including that of the Clock applet, the following rule of thumb will guide you to the best option.

 


Rule of Thumb:  If your class must subclass some other class (the most common example being Applet), you should use Runnable as described in option #2.

To run in a Java-enabled browser, the Clock class has to be a subclass of the Applet class. Also, the Clock applet needs a thread so that it can continuously update its display without taking over the process in which it is running. (Some browsers might create a new thread for each applet so as to prevent a misbehaved applet from taking over the main browser thread. However, you should not count on this when writing your applets; your applets should create their own threads when doing computer-intensive work.) But since the Java language does not support multiple class inheritance, the Clock class cannot be a subclass of both Thread and Applet. Thus the Clock class must use the Runnable interface to provide its threaded behavior.

The Life Cycle of a Thread

The following diagram shows the states that a Java thread can be in during its life. It also illustrates which method calls cause a transition to another state. This figure is not a complete finite state diagram, but rather an overview of the more interesting and common facets of a thread's life. The remainder of this section uses the Clock applet previously introduced to discuss a thread's life cycle in terms of its state.

 

Creating a Thread

The application in which an applet is running calls the applet's start method when the user visits the applet's page. The Clock applet creates a Thread, clockThread, in its start with the bold code shown here:
public void start() {
    if (clockThread == null) {
        clockThread = new Thread(this, "Clock");
        clockThread.start();
    }
}    
After the bold statement has been executed, clockThread is in the New Thread state. When a thread is a New Thread, it is merely an empty Thread object; no system resources have been allocated for it yet. When a thread is in this state, you can only start the thread. Calling any method besides start when a thread is in this state makes no sense and causes an IllegalThreadStateException. (In fact, the runtime system throws an IllegalThreadStateException any time a method is called on a thread and that thread's state does not allow for that method call.)

Notice that this--the Clock instance-- is the first argument to the thread constructor. The first argument to this thread constructor must implement the Runnable interface and provides the thread with its run method. The second argument is just a name for the thread.

Starting a Thread

Now consider the next line of code in Clock's start method shown here in bold:
public void start() {
    if (clockThread == null) {
        clockThread = new Thread(this, "Clock");
        clockThread.start();
    }
}  
The start method creates the system resources necessary to run the thread, schedules the thread to run, and calls the thread's run method. clockThread's run method is the one defined in the Clock class.

After the start method has returned, the thread is "running". Yet, it's somewhat more complex than that. As the previous figure shows, a thread that has been started is actually in the Runnable state. Many computers have a single processor, thus making it impossible to run all "running" threads at the same time. The Java runtime system must implement a scheduling scheme that shares the processor between all "running" threads. (See Understanding Thread Priority for more information about scheduling.) So at any given time, a "running" thread actually may be waiting for its turn in the CPU.

Here's another look at Clock's run method:

public void run() {
   Thread myThread = Thread.currentThread();
   while (clockThread == myThread) {
        repaint();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e){
            // the VM doesn't want us to sleep anymore,
            // so get back to work
        }
    }
}
Clock's run method loops while the condition clockThread == myThread is true. This exit condition is explained in more detail in Stopping a Thread. However, for now, know that it allows the thread, and thus the applet, to exit gracefully.

Within the loop, the applet repaints itself and then tells the thread to sleep for one second (1000 milliseconds). An applet's repaint method ultimately calls the applet's paint method, which does the actual update of the applet's display area. The Clock paint method gets the current time, formats, and displays it:

public void paint(Graphics g) {
    // get the time and convert it to a date
    Calendar cal = Calendar.getInstance();
    Date date = cal.getTime();
    // format it and display it
    DateFormat dateFormatter = DateFormat.getTimeInstance();
    g.drawString(dateFormatter.format(date), 5, 10);
}
 

Making a Thread Not Runnable

A thread becomes Not Runnable when one of these events occurs:
  • Its sleep method is invoked.
  • The thread calls the wait method to wait for a specific condition to be satisifed.
  • The thread is blocking on I/O.
The clockThread in the Clock applet becomes Not Runnable when the run method calls sleep on the current thread:
public void run() {
    Thread myThread = Thread.currentThread();
    while (clockThread == myThread) {
        repaint();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e){
            // the VM doesn't want us to sleep anymore,
            // so get back to work
        }
    }
}
During the second that the clockThread is asleep, the thread does not run, even if the processor becomes available. After the second has elapsed, the thread becomes Runnable again and, if the processor becomes available, the thread begins running again.

For each entrance into the Not Runnable state, there is a specific and distinct escape route that returns the thread to the Runnable state. An escape route works only for its corresponding entrance. For example, if a thread has been put to sleep, then the specified number of milliseconds must elapse before the thread becomes Runnable again. The following list describes the escape route for every entrance into the Not Runnable state:

  • If a thread has been put to sleep, then the specified number of milliseconds must elapse.
  • If a thread is waiting for a condition, then another object must notify the waiting thread of a change in condition by calling notify or notifyAll. More information is available in Synchronizing Threads.
  • If a thread is blocked on I/O, then the I/O must complete.

Stopping a Thread

A program doesn't stop a thread like it stops an applet (by calling a method). Rather, a thread arranges for its own death by having a run method that terminates naturally. For example, the while loop in this run method is a finite loop-- it will iterate 100 times and then exit:
public void run() {
    int i = 0;
    while (i < 100) {
        i++;
        System.out.println("i = " + i);
    }
}
A thread with this run method dies naturally when the loop completes and the run method exits.

Let's look at how the Clock applet thread arranges for its own death. You might want to use this technique with your applets. Recall Clock's run method:

public void run() {
    Thread myThread = Thread.currentThread();
    while (clockThread == myThread) {
        repaint();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e){
            // the VM doesn't want us to sleep anymore,
            // so get back to work
        }
    }
}
The exit condition for this run method is the exit condition for the while loop because there is no code after the while loop:
while (clockThread == myThread) {
This condition indicates that the loop will exit when the currently executing thread is not equal to clockThread. When would this ever be the case?

When you leave the page, the application in which the applet is running calls the applet's stop method. This method then sets the clockThread to null, thereby telling the main loop in the run method to terminate:

public void stop() {    // applets' stop method
    clockThread = null;
}
If you revisit the page, the start method is called again and the clock starts up again with a new thread. Even if you stop and start the applet faster than one iteration of the loop, clockThread will be a different thread than myThread and the loop will still terminate.

The isAlive Method

A final word about thread state: The API for the Thread class includes a method called isAlive. The isAlive method returns true if the thread has been started and not stopped. If the isAlive method returns false, you know that the thread either is a New Thread or is Dead. If the isAlive method returns true, you know that the thread is either Runnable or Not Runnable. You cannot differentiate between a New Thread or a Dead thread. Nor can you differentiate between a Runnable thread and a Not Runnable thread.

 

Understanding Thread Priority

Previously, this lesson claimed that threads run concurrently. While conceptually this is true, in practice it usually isn't. Most computer configurations have a single CPU, so threads actually run one at a time in such a way as to provide an illusion of concurrency. Execution of multiple threads on a single CPU, in some order, is called scheduling. The Java runtime supports a very simple, deterministic scheduling algorithm known as fixed priority scheduling. This algorithm schedules threads based on their priority relative to other runnable threads.

When a Java thread is created, it inherits its priority from the thread that created it. You can also modify a thread's priority at any time after its creation using the setPriority method. Thread priorities are integers ranging between MIN_PRIORITY and MAX_PRIORITY (constants defined in the Thread class). The higher the integer, the higher the priority. At any given time, when multiple threads are ready to be executed, the runtime system chooses the runnable thread with the highest priority for execution. Only when that thread stops, yields, or becomes not runnable for some reason will a lower priority thread start executing. If two threads of the same priority are waiting for the CPU, the scheduler chooses one of them to run in a round-robin fashion. The chosen thread will run until one of the following conditions is true:

  • A higher priority thread becomes runnable.
  • It yields, or its run method exits.
  • On systems that support time-slicing, its time allotment has expired.
Then the second thread is given a chance to run, and so on, until the interpreter exits.

The Java runtime system's thread scheduling algorithm is also preemptive. If at any time a thread with a higher priority than all other runnable threads becomes runnable, the runtime system chooses the new higher priority thread for execution. The new higher priority thread is said to preempt the other threads.

 


Rule of thumb:  At any given time, the highest priority thread is running. However, this is not guaranteed. The thread scheduler may choose to run a lower priority thread to avoid starvation. For this reason, use priority only to affect scheduling policy for efficiency purposes. Do not rely on thread priority for algorithm correctness.

A Thread Race

RaceApplet is an applet that animates a race between two "runner" threads with different priorities. The top runner, labelled "2", has a priority of 2. The second runner, labelled "3", has a priority of 3.

 

Click this figure to run the applet.

This is the run method for both runners

public int tick = 1;
public void run() {
    while (tick < 10000000)
        tick++;
} 
This run method simply counts from 1 to 10,000,000. The instance variable tick is public because the applet uses this value to determine how far the runner has progressed (how long its line is).

In addition to the two runner threads, this applet also has a third thread that handles the drawing. The drawing thread's run method contains an infinite loop; during each iteration of the loop it draws a line for each runner (whose length is computed from the runner's tick variable), and then sleeps for 3 milliseconds. The drawing thread has a thread priority of 4--higher than either runner. So, whenever the drawing thread wakes up after 3 milliseconds, it becomes the highest priority thread, preempting whichever runner is currently running, and draws the lines. You can see the lines inch their way across the page

This is not a fair race because one runner has a higher priority than the other. Each time the drawing thread yields the CPU by going to sleep for 3 milliseconds, the scheduler chooses the highest priority runnable thread to run; in this case, it's always the runner with a priority of 3. Here is another version of the applet that implements a fair race, that is, both of the runners have the same priority and they have an equal chance of being chosen to run.

 

Click this figure to run the applet.

In this race, each time the drawing thread yields the CPU by going to sleep, there are two runnable threads of equal priority--the runners--waiting for the CPU; the scheduler must choose one of the threads to run. In this situation, the scheduler chooses the next thread to run in a round-robin fashion.

Selfish Threads

The Runner class used in the races above actually implements "socially-impaired" thread behavior. Recall the run method from the Runner class used in the races above:
public int tick = 1;
public void run() {
    while (tick < 10000000)
        tick++;
} 
The while loop in the run method is in a tight loop. Once the scheduler chooses a thread with this thread body for execution, the thread never voluntarily relinquishes control of the CPU--the thread continues to run until the while loop terminates naturally or until the thread is preempted by a higher priority thread. This thread is called a selfish thread.

In some situations, having selfish threads doesn't cause any problems because a higher priority thread preempts the selfish one (just as the drawing thread in the RaceApplet preempts the selfish runners). However, in other situations, threads with CPU-greedy run methods, such as the Runner class, can take over the CPU and cause other threads to wait for a long time before getting a chance to run.

Time-Slicing

Some systems, such as Windows 95/NT, fight selfish thread behavior with a strategy known as time-slicing. Time-slicing comes into play when there are multiple "Runnable" threads of equal priority and those threads are the highest priority threads competing for the CPU. For example, this creates two equal priority selfish threads that have the following run method.
public void run() {
    while (tick < 400000) {
        tick++;
        if ((tick % 50000) == 0)
            System.out.println("Thread #" + num
                               + ", tick = " + tick);
    }
}    
This run contains a tight loop that increments the integer tick and every 50,000 ticks prints out the thread's identifier and its tick count.

When running this program on a time-sliced system, you will see messages from both threads intermingled with one another. Like this:

Thread #1, tick = 50000
Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #1, tick = 250000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #1, tick = 300000
Thread #1, tick = 350000
Thread #0, tick = 350000
Thread #0, tick = 400000
Thread #1, tick = 400000
This output is produced because a time-sliced system divides the CPU into time slots and iteratively gives each of the equal-and-highest priority threads a time slot in which to run. The time-sliced system iterates through the equal-and-highest priority threads, allowing each one a bit of time to run, until one or more of them finishes or until a higher priority thread preempts them. Notice that time-slicing makes no guarantees as to how often or in what order threads are scheduled to run.

When running this program on a non-time-sliced system, however, you will see messages from one thread finish printing before the other thread ever gets a chance to print one message. Like this:

Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #0, tick = 350000
Thread #0, tick = 400000
Thread #1, tick = 50000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #1, tick = 250000
Thread #1, tick = 300000
Thread #1, tick = 350000
Thread #1, tick = 400000
This is because a non-time-sliced system chooses one of the equal-and-highest priority threads to run and allows that thread to run until it relinquishes the CPU (by sleeping, yielding, finishing its job) or until a higher priority preempts it.

 


Note:  The Java runtime does not implement (and therefore does not guarantee) time-slicing. However, some systems on which you can run Java do support time-slicing. Your Java programs should not rely on time-slicing as it may produce different results on different systems.

As you can imagine, writing CPU-intensive code can have negative repercussions on other threads running in the same process. In general, you should try to write "well-behaved" threads that voluntarily relinquish the CPU periodically and give other threads an opportunity to run. In particular, you should never write Java code that relies on time-sharing--this will practically guarantee that your program will give different results on different computer systems.

A thread can voluntarily yield the CPU without going to sleep or some other drastic means by calling the yield method. The yield method gives other threads of the same priority a chance to run. If there are no equal priority threads that are runnable, then the yield is ignored.

Summary

  • Most computers have only one CPU, so threads must share the CPU with other threads. The execution of multiple threads on a single CPU, in some order, is called scheduling. The Java runtime supports a very simple, deterministic scheduling algorithm known as fixed priority scheduling.
  • Each Java thread is given a numeric priority between MIN_PRIORITY and MAX_PRIORITY (constants defined in the Thread class). At any given time, when multiple threads are ready to be executed, the thread with the highest priority is chosen for execution. Only when that thread stops, or is suspended for some reason, will a lower priority thread start executing.
  • Scheduling of the CPU is fully preemptive. If a thread with a higher priority than the currently executing thread needs to execute, the higher priority thread is immediately scheduled.
  • The Java runtime will not preempt the currently running thread for another thread of the same priority. In other words, the Java runtime does not time-slice. However, the system implementation of threads underlying the Java Thread class may support time-slicing. Do not write code that relies on time-slicing.
  • In addition, a given thread may, at any time, give up its right to execute by calling the yield method. Threads can only yield the CPU to other threads of the same priority--attempts to yield to a lower priority thread are ignored.
  • When all the runnable threads in the system have the same priority, the scheduler chooses the next thread to run in a simple, non-preemptive, round-robin scheduling order.

Synchronizing Threads

So far, this lesson has contained examples with independent, asynchronous threads. That is, each thread contained all of the data and methods required for its execution and didn't require any outside resources or methods. In addition, the threads in those examples ran at their own pace without concern over the state or activities of any other concurrently running threads.

However, there are many interesting situations where separate, concurrently running threads do share data and must consider the state and activities of other threads. One such set of programming situations are known as producer/consumer scenarios where the producer generates a stream of data which then is consumed by a consumer.

For example, imagine a Java application where one thread (the producer) writes data to a file while a second thread (the consumer) reads data from the same file. Or, as you type characters on the keyboard, the producer thread places key events in an event queue and the consumer thread reads the events from the same queue. Both of these examples use concurrent threads that share a common resource: the first shares a file, the second shares an event queue. Because the threads share a common resource, they must be synchronized in some way.

The Producer generates an integer between 0 and 9 (inclusive), stores it in a CubbyHole object, and prints the generated number. To make the synchronization problem more interesting, the Producer sleeps for a random amount of time between 0 and 100 milliseconds before repeating the number generating cycle:
public class Producer extends Thread {
    private CubbyHole cubbyhole;
    private int number;

    public Producer(CubbyHole c, int number) {
        cubbyhole = c;
        this.number = number;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            cubbyhole.put(i);
            System.out.println("Producer #" + this.number
                               + " put: " + i);
            try {
                sleep((int)(Math.random() * 100));
            } catch (InterruptedException e) { }
        }
    }
}
The Consumer, being ravenous, consumes all integers from the CubbyHole (the exact same object into which the Producer put the integers in the first place) as quickly as they become available.
public class Consumer extends Thread {
    private CubbyHole cubbyhole;
    private int number;

    public Consumer(CubbyHole c, int number) {
        cubbyhole = c;
        this.number = number;
    }

    public void run() {
        int value = 0;
        for (int i = 0; i < 10; i++) {
            value = cubbyhole.get();
            System.out.println("Consumer #" + this.number
                               + " got: " + value);
        }
    }
}

The Producer and Consumer in this example share data through a common CubbyHole object. And you will note that neither the Producer nor the Consumer makes any effort whatsoever to ensure that the Consumer is getting each value produced once and only once. The synchronization between these two threads actually occurs at a lower level, within the get and put methods of the CubbyHole object. However, let's assume for a moment that these two threads make no arrangements for synchronization and talk about the potential problems that might arise in that situation.

One problem arises when the Producer is quicker than the Consumer and generates two numbers before the Consumer has a chance to consume the first one. Thus the Consumer would skip a number. Part of the output might look like this:

    . . .

Consumer #1 got: 3
Producer #1 put: 4
Producer #1 put: 5
Consumer #1 got: 5

    . . .
Another problem that might arise is when the Consumer is quicker than the Producer and consumes the same value twice. In this situation, the Consumer would print the same value twice and might produce output that looked like this:
    . . .

Producer #1 put: 4
Consumer #1 got: 4
Consumer #1 got: 4
Producer #1 put: 5

    . . .
Either way, the result is wrong. You want the Consumer to get each integer produced by the Producer exactly once. Problems such as those just described are called race conditions. They arise from multiple, asynchronously executing threads trying to access a single object at the same time and getting the wrong result.

Race conditions in the producer/consumer example are prevented by having the storage of a new integer into the CubbyHole by the Producer be synchronized with the retrieval of an integer from the CubbyHole by the Consumer. The Consumer must consume each integer exactly once.

The activities of the Producer and Consumer must be synchronized in two ways. First, the two threads must not simultaneously access the CubbyHole. A Java thread can prevent this from happening by locking an object. When an object is locked by one thread and another thread tries to call a synchronized method on the same object, the second thread will block until the object is unlocked. Locking an Object discusses this.

And second, the two threads must do some simple coordination. That is, the Producer must have some way to indicate to the Consumer that the value is ready and the Consumer must have some way to indicate that the value has been retrieved. The Thread class provides a collection of methods--wait, notify, and notifyAll--to help threads wait for a condition and notify other threads of when that condition changes. Using the notifyAll and wait Methods has more information.

The Main Program

Here's a small stand-alone Java application that creates a CubbyHole object, a Producer, a Consumer, and then starts both the Producer and the Consumer.

public class ProducerConsumerTest {
    public static void main(String[] args) {
        CubbyHole c = new CubbyHole();
        Producer p1 = new Producer(c, 1);
        Consumer c1 = new Consumer(c, 1);

        p1.start();
        c1.start();
    }
}

The Output

Here's the output of ProducerConsumerTest.

Producer #1 put: 0
Consumer #1 got: 0
Producer #1 put: 1
Consumer #1 got: 1
Producer #1 put: 2
Consumer #1 got: 2
Producer #1 put: 3
Consumer #1 got: 3
Producer #1 put: 4
Consumer #1 got: 4
Producer #1 put: 5
Consumer #1 got: 5
Producer #1 put: 6
Consumer #1 got: 6
Producer #1 put: 7
Consumer #1 got: 7
Producer #1 put: 8
Consumer #1 got: 8
Producer #1 put: 9
Consumer #1 got: 9

Locking an Object

The code segments within a program that access the same object from separate, concurrent threads are called critical sections. In the Java language, a critical section can be a block or a method and are identified with the synchronized keyword. The Java platform then associates a lock with every object that has synchronized code.

In the producer/consumer example, the put and get methods of the CubbyHole are the critical sections. The Consumer should not access the CubbyHole when the Producer is changing it, and the Producer should not modify it when the Consumer is getting the value. So put and get in the CubbyHole class should be marked with the synchronized keyword.

Here's a code skeleton for the CubbyHole class:

public class CubbyHole {
    private int contents;
    private boolean available = false;

    public synchronized int get() {
        ...
    }

    public synchronized void put(int value) {
        ...
    }
}

Note that the method declarations for both put and get contain the synchronized keyword. Hence, the system associates a unique lock with every instance of CubbyHole (including the one shared by the Producer and the Consumer). Whenever control enters a synchronized method, the thread that called the method locks the object whose method has been called. Other threads cannot call a synchronized method on the same object until the object is unlocked.

So, when the Producer calls CubbyHole's put method, it locks the CubbyHole, thereby preventing the Consumer from calling the CubbyHole's get method:

public synchronized void put(int value) {
    // CubbyHole locked by the Producer
    ..
    // CubbyHole unlocked by the Producer
}

When the put method returns, the Producer unlocks the CubbyHole.

Similarly, when the Consumer calls CubbyHole's get method, it locks the CubbyHole, thereby preventing the Producer from calling put:

public synchronized int get() {
    // CubbyHole locked by the Consumer
    ...
    // CubbyHole unlocked by the Consumer
}

The acquisition and release of a lock is done automatically and atomically by the Java runtime system. This ensures that race conditions cannot occur in the underlying implementation of the threads, thus ensuring data integrity. Synchronization isn't the whole story. The two threads must also be able to notify one another when they've done their job. Learn more about that after a brief foray into reentrant locks.

Using the notifyAll and wait Methods

You really want the Consumer to wait until the Producer puts something in the CubbyHole and the Producer must notify the Consumer when it's done so. Similarly, the Producer must wait until the Consumer takes a value (and notifies the Producer of its activities) before replacing it with a new value. The two threads must coordinate more fully and can use Object's wait and notifyAll methods to do so.

Here are the new implementations of get and put that wait on and notify each other of their activities:

public synchronized int get() {
    while (available == false) {
        try {
            // wait for Producer to put value
            wait();
        } catch (InterruptedException e) {
        }
    }
    available = false;
    // notify Producer that value has been retrieved
    notifyAll();
    return contents;
}
public synchronized void put(int value) {
    while (available == true) {
        try {
            // wait for Consumer to get value
            wait();
        } catch (InterruptedException e) {
        }
    }
    contents = value;
    available = true;
    // notify Consumer that value has been set
    notifyAll();
} 
The code in the get method loops until the Producer has produced a new value. Each time through the loop, get calls the wait method. The wait method relinquishes the lock held by the Consumer on the CubbyHole (thereby allowing the Producer to get the lock and update the CubbyHole) and then waits for notification from the Producer. When the Producer puts something in the CubbyHole, it notifies the Consumer by calling notifyAll. The Consumer then comes out of the wait state, available is now true, the loop exits, and the get method returns the value in the CubbyHole.

The put method works in a similar fashion, waiting for the Consumer thread to consume the current value before allowing the Producer to produce a new one.

The notifyAll method wakes up all threads waiting on the object in question (in this case, the CubbyHole). The awakened threads compete for the lock. One thread gets it, and the others go back to waiting. The Object class also defines the notify method, which arbitrarily wakes up one of the threads waiting on this object.

The Object class contains not only the version of wait that is used in the producer/consumer example and which waits indefinitely for notification, but also two other versions of the wait method:

wait(long timeout)
Waits for notification or until the timeout period has elapsed. timeout is measured in milliseconds.
wait(long timeout, int nanos)
Waits for notification or until timeout milliseconds plus nanos nanoseconds have elapsed.

Note:  Besides using these timed wait methods to synchronize threads, you also can use them in place of sleep. Both wait and sleep delay for the requested amount of time, but you can easily wake up wait with a notify but a sleeping thread cannot be awakened prematurely. This doesn't matter too much for threads that don't sleep for long, but it could be important for threads that sleep for minutes at a time.

 

Avoiding Starvation and Deadlock

The dining philosophers are often used to illustrate various problems that can occur when many synchronized threads are competing for limited resources.

The story goes like this: Five philosophers are sitting at a round table. In front of each philosopher is a bowl of rice. Between each pair of philosophers is one chopstick. Before an individual philosopher can take a bite of rice he must have two chopsticks--one taken from the left, and one taken from the right. The philosophers must find some way to share chopsticks such that they all get to eat.

The following applet does a rough animation using an image of Duke for the dining philosophers. This particular algorithm works as follows: Duke always reaches for the chopstick on his right first. If the chopstick is there, Duke takes it and raises his right hand. Next, Duke tries for the left chopstick. If the chopstick is available, Duke picks it up and raises his other hand. Now that Duke has both chopsticks, he takes a bite of rice and says "Mmm!" He then puts both chopsticks down, allowing either of his two neighbors to get the chopsticks. Duke then starts all over again by trying for the right chopstick. Between each attempt to grab a chopstick, each Duke pauses for a random period of time.

Click this figure to run the applet.
This is a picture of the applet's GUI. To run the applet, click the picture. The applet will appear in a new browser window.

The slider controls the amount of time that each philosopher will wait before attempting to pick up a chopstick. When the slider is set to 0, the philosophers don't wait--they just grab--and most often, the applet ends up in deadlock: all the philosophers are frozen with their right hand in the air. Why? Because each philosopher immediately has one chopstick and is waiting on a condition that cannot be satisfied--they are all waiting for the left chopstick, which is held by the philosopher to their left.

When you move the slider so that the waiting period is longer, the applet may proceed for a while without deadlocking. However, deadlock is always possible with this particular implementation of the dining philosophers problem because it is possible for all five philosophers to be holding their right chopsticks. Rather than rely on luck to prevent deadlock, you must either prevent it or detect it.

For most Java programmers, the best choice is to prevent deadlock rather than to try and detect it. Deadlock detection is complicated and beyond the scope of this tutorial. The simplest approach to preventing deadlock is to impose ordering on the condition variables. In the dining philosopher applet, there is no ordering imposed on the condition variables because the philosophers and the chopsticks are arranged in a circle. All chopsticks are equal.

However, we can change the rules in the applet by numbering the chopsticks 1 through 5 and insisting that the philosophers pick up the chopstick with the lower number first. The philosopher who is sitting between chopsticks 1 and 2 and the philosopher who is sitting between chopsticks 1 and 5 must now reach for the same chopstick first (chopstick 1) rather than picking up the one on the right. Whoever gets chopstick 1 first is now free to take another one. Whoever doesn't get chopstick 1 must now wait for the first philosopher to release it. Deadlock is not possible.