Threads: Concurrent Programming
AAHC

Click on A to make all fonts on the page smaller.

Click on A to make all fonts on the page larger.

Click on HC to toggle high contrast mode. When you move your mouse over some bold words in high contrast mode, related words are automatically highlighted. Text is shown in black and white.


Behind the Scenes

In concurrent programming, there are two basic units of execution: process and thread.

A process has a self-contained execution environment. Most computer users use processes without knowing it. Processes are usually located at the level of operating systems software, or kernel-level entities. A process generally has a complete, private set of basic run-time resources. Specifically, each process has its own registers, program counter, stack pointer, and memory space.

Threads exist within a process-—every process has at least one thread. In Java, concurrent programming is accomplished mostly through threads. Sometimes threads are called lightweight processes or execution contexts. Both processes and threads provide an execution environment (like processes, threads have their own registers, program counters, stack pointers, etc.), but threads are closer to a user-level entity. We can create threads and tell them what to do using fewer resources than we do when we create and inform new processes.

NoteMultiple threads running at the same time and sharing resources have the potential to cause problems. We'll go over some of these issues in this lesson. We'll talk about others in the next lesson when we explore synchronization. For now, we'll focus on plain old multi-tasking.


Multi-threaded Applications
The Life of a Thread

Let's start with an example of multi-tasking. In the editor, open your java4_Lesson8 folder to the demo package and open the ThreadedApplet class. Edit ThreadedApplet as shown in blue below:

CODE TO TYPE: ThreadedApplet
package demo;

import java.awt.Graphics;
import java.applet.Applet;

public class ThreadedApplet extends Applet implements Runnable {

    Thread appletThread;  // the thread we make will be an instance of the class Thread
    String messages[] = {"Hello Thread World!" , "I'm doing fine." , "Goodbye for now!"};
    int i = 0;

    public void paint(Graphics g) {
        g.drawString(messages[i], 15, 50);
    }

    public void run() {
        while (true){
            i = (i+1)  % messages.length;
            repaint();
            System.out.println("Hey! I'm still here");
            try {
                appletThread.sleep(5000);
            } catch (InterruptedException e){}
        }
    }

    public void start() {
        appletThread = new Thread(this);
        appletThread.start();
    }   
}

Save and run it. Watch it run for a while, then in the Applet Viewer Window, select Applet | Stop to stop it. Now, watch the Console for a minute.

Our Applet has stopped (it's no longer painting), but its Thread is still running (it's still putting output into the Console). Quit the Applet to close the Applet Viewer Window; the action in the Console stops.

What's Happening in the Background?

Our ThreadedApplet illustrates that we need to take care when using multiple threads. Have you ever opened a web page that seemed to slow your machine down--even after you left the page? Our example shows us the reason behind that. We stopped the Applet (which happens when you leave a browser page that's running the applet), but we did not stop our Applet's thread. Remember--stopping the thread and stopping the applet are two distinct processes.

We will fix this by making our code cleaner, but first let's go over one more background item.

Garbage Collection

One example of a thread working in the background in Java is in garbage collection--retrieving memory that has been allocated, but is no longer being used. Java collects garbage using Threads. While you are running your program, the Java Virtual Machine has a garbage collection thread cleaning up in the background.

Here are some links to more information on this topic. This JavaWorld article explains the concept of garbage collection. Oracle also provides a useful page that explains Tuning Garbage Collection.


Thread States

The first Java tutorial on Threads provides the state transition diagram below described as "an overview of the interesting and common facets of a thread's life":


Each oval in the diagram represents a possible state of the thread. The arrows represent potential transitions among the states. We will give you a new version of our ThreadedApplet and comment in the code when the thread is in one of those potential states listed.

Edit ThreadedApplet as shown in blue:

CODE TO EDIT: ThreadedApplet
package demo;

import java.awt.Graphics;
import java.applet.Applet;

public class ThreadedApplet extends Applet implements Runnable {

    Thread appletThread;
    String messages[] = {"Hello Thread World!" , "I'm doing fine." , "Good-bye for now!"};
    int i = 0;

    public void paint(Graphics g) {
	    g.drawString(messages[i], 15, 50);
    }

    public void run() {
        while (appletThread != null) {  // checks if Thread exists
            i = (i + 1) % messages.length;
            repaint();
            System.out.println("Hey! I'm still here");
            try { 
                appletThread.sleep(5000);   // sleep--put in Not Runnable State (TIMED_WAITING)
            } catch (InterruptedException e){ }
        }
    }

    public void start() {             // start of Applet
        if (appletThread == null) {
            appletThread = new Thread(this);  // new Thread()--achieve New Thread State
            appletThread.start();             // start of Thread--achieve Runnable State
        }
    }
    
    public void stop() {                      // stop Applet
        appletThread = null;                  // stop Thread by destroying--put in Dead State
    }
}

Save and run it. Watch a while--at least until it cycles--and then, in the Applet Viewer Window, select Applet | Stop to stop it. Now look at the Console for a bit:

This time, when our Applet was stopped (no longer painting), its Thread was also stopped (no new output in Console). This is because when we stopped the Applet, we actually killed the thread (we made it null), but we aren't done yet!

Restart the Applet (Applet | Restart). When we Restart, it calls the start() method of the Applet, which starts a new Thread. The thread begins again--both in the Applet's paint() method and in the Console. Now, try to Start the Applet (Applet | Start). Look at the bottom of the Applet Viewer Window:

This time, the Applet was already in the start state, as was its Thread. A thread cannot be started with the start() method after it has already been started (click here to learn more about start() for Threads). That's why we checked first to find out if the thread already existed in the start() method for the applet. If it did not exist, we would've created it. If it had existed, we wouldn't have had to do anything because it still would've been there, even if the applet had been delayed for a while. Let's look more closely at thread states and see what else is possible.


Thread.State

In order to start the thread, we called a start() method for the thread inside of the start() method for the applet. So why don't we do the same thing to stop? We wrote a stop() method for our applet, so why did we kill the thread (by making it null) rather than call a thread stop() method?

Go to java.lang.Thread. Scroll to the Method Summary. Check out these methods: destroy(), resume(), stop(), and suspend().

In each of these methods you see the word deprecated and a description of the reason. Each new release of Java includes new functionality and fixes. If a method is deprecated, it means that the method should not be used in new code because a problem was found that could not be fixed, but that method is retained (perhaps temporarily) in order to maintain compatibility with older versions of Java. There is an inherent problem with the older implementation, so newer versions of Java should not use it. If you are responsible for maintaining code that contains a deprecated method, replace that method if possible.

Each deprecated method points to the Oracle tutorial Java Thread Primitive Deprecation.

Threads was altered in version 1.5 of Java to alleviate problems with deprecated methods. They also want to make sure that users don't encounter problems due to the speed of computers by adding Enum States for Threads. Let's take a look at those changes.

Go to java.lang.Thread. Scroll to the Nested Class Summary. Click on Thread.State. Scroll to the description of the states:

If Oracle created Enum Constants in their own Enum class Thread.State, then Thread states must be important. This particular Thread.State class is dedicated solely to providing an enumeration of the states and methods that indentify those states.


More on Multi-Threaded Applications
Design Pattern: Producer/Consumer

The design pattern of Producers and Consumers used in conjunction with threads is common in Java programming. As is often the case, many producers supply a product or resource, and many consumers take the product or resource. A shared resource is one that many consume. The resource is available at some location (like a store or warehouse), and inventory must be maintained and tracked. Problems may occur when there is not enough supply available to meet the demand, or if the "storehouse" takes in more than it can handle. To avoid such problems, we need a monitor to keep inventory of our resource.

We'll include a monitor when we create our application. Our threaded applications will have these three components:

  • Producer: supplies the resource.
  • Consumer: uses the resource.
  • Monitor: keeps a running inventory of the resource.

Using Threads in an Application

Our first example using threads will involve alphabet soup. In this example, there is a child who demands that his soup contain more than just broth, so his parent produces alphabet letters to add to his soup. The child can then consume these letters. The design pattern we described above materializes in our example like this:

  • Producer: the parent.
  • Consumer: the child.
  • Monitor: alphabet soup (stay with me on this).
The Producer

Make a new java4_Lesson9 project. If you're given the option to "Open Associated Perspective", click No. In this project, create a new Class as shown:

Type Producer as shown in blue:

CODE TO TYPE: Producer
package prodcons;

class Producer extends Thread {
    private Soup soup;
    private String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private MyTableSetting bowlView;
 
    public Producer(MyTableSetting bowl, Soup s) {
        bowlView = bowl;        // the producer is given the GUI that will show what is happening
        soup = s;               // the producer is given the soup--the monitor
    }

    public void run() {
        String c;
        for (int i = 0; i < 10; i++) {                           // only put in 10 things so it will stop
            c = String.valueOf(alphabet.charAt((int)(Math.random() * 26)));   // randomly pick a number to associate with an alphabet letter
            soup.add(c);                                            // add it to the soup
            System.out.println("Added " + c + " to the soup.");     // show what happened in Console
            bowlView.repaint();                                     // show it in the bowl  

            try {
                sleep((int)(Math.random() * 2000));  // sleep for a while so it is not too fast to see
            } catch (InterruptedException e) { }
        }
    }
}

Save it.

The Consumer

In java4_Lesson9, create a new Consumer class as shown:

Type Consumer as shown in blue:

CODE TO TYPE: Consumer
package prodcons;

class Consumer extends Thread {
    private Soup soup;
    private MyTableSetting bowlView;

    public Consumer(MyTableSetting bowl, Soup s) {
        bowlView = bowl;                               // the consumer is given the GUI that will show what is happening
        soup = s;                                      // the consumer is given the soup--the monitor
    }

    public void run() {
        String c;
        for (int i = 0; i < 10; i++) {              // stop thread when know there are no more coming; here we know there will only be 10
            c = soup.eat();                            // eat it from the soup
            System.out.println("Ate a letter: " + c);  // show what happened in Console
            bowlView.repaint();                        // show it in the bowl  

            try {
                sleep((int)(Math.random() * 3000));    // have consumer sleep a little longer or sometimes we never see the alphabets!
            } catch (InterruptedException e) { }
        }
    }
}

Save it.

The Monitor

To ensure that shared information within the monitor doesn't become corrupted, we'll synchronize the add and remove methods in this class. Synchronization prevents multiple methods from accessing a shared resource simultaneously. If a method in a class is synchronized, it BLOCKs other Threads from accessing any other synchronized methods in that instance of that class.

A class with synchronized methods provides a lock and prevents what are known as deadlock conditions.

Here's how a deadlock condition might look in real life. Let's say we're waiting in line in a bank. I am at the front of the line, waiting to withdraw cash. The bank is out of cash, but I am willing to wait for some cash to be deposited. The bank only has one teller, who cannot handle another transaction until the current transaction is finished. I am still waiting to receive my money, so my transaction is not finished. You are in line behind me with a million dollars to deposit. You can't deposit the money until I finish my transaction, and I will not be finished until someone deposits some money. We are in a deadlock.

In our example, we'll use synchronized blocks of code to prevent deadlock. A few other elements of our example you should also be aware of:

  • Only 6 alphabet letters may be in the soup at a given time, so we set the variable capacity to 6.
  • We'll determine whether there are alphabet letters in the soup (the buffer) before we take any out.
  • We'll determine whether the soup is full (that is, at it's capacity of 6 letters) before we add any more.

Two important methods for Threads will be used during this process: notifyAll() and wait()

Go to java.lang.Thread in the API. Scroll down to the Method Summary. Look for the methods notifyAll() and wait(). Remember to look at all of the methods for Threads--including the ones inherited from Object.

In java4_Lesson9, create a new Soup class as shown:

Type Soup as shown in blue:

CODE TO TYPE: Soup
package prodcons;

import java.util.*;

public class Soup {
    private ArrayList <String> buffer = new ArrayList<String>();  // buffer holds what is in the soup
    private int capacity = 6;                                                 // limit to 6 alphabet pieces

    public synchronized String eat() {                                        // synchronized makes others BLOCKED
        while(buffer.isEmpty()){                                              // cannot eat if nothing is there, so check to see if it is empty                                                 
            try {
                wait();                                                       // if so, we WAIT until someone puts something there
            } catch (InterruptedException e) {}                               // doing so temporarily allows other synchronized methods to run (specifically - add)
        }                                                                     // we will not get out of this while until something is there to eat
        String toReturn = buffer.get((int)(Math.random() * buffer.size()));   // get a random alphabet in the soup     
        buffer.remove(toReturn);                                              // remove it so no one else can eat it
        buffer.trimToSize();                                                  // reduce the size of the buffer to fit how many pieces are there
        notifyAll();                                                          // tell anyone WAITing that we have eaten something and are done
        return(toReturn);                                                     // actually return the alphabet piece to the consumer who asked to eat it
    }

    public synchronized void add(String c) {                                  // synchronized makes others BLOCKED
        while (buffer.size() == capacity) {                                   // cannot add more pieces if the buffer is full to capacity
            try {
                wait();                                                       // if so, we WAIT - temporarily allows other synchronized methods to run - i.e., eat()
            } catch (InterruptedException e) {}
        }                                                                     // we will not get out of this while until something has been eaten to make room
        buffer.add(c);                                                        // add another alphabet piece to the soup 
        notifyAll();                                                          // tell anyone WAITing that we have added something and are done
    }

    public ArrayList <String> getContents(){                            // we want to be able to get the contents so we can show them in the GUI view
        return (buffer);                                                      // multiple problems with this - we will address later
    }
}

Save it.


The GUI View

Now let's put it all together, using an Applet to provide a nice view for our users.

In java4_Lesson9, add a new MyTableSetting class as shown:

Type MyTableSetting as shown in blue:

CODE TO TYPE: MyTableSetting
package prodcons;

import java.applet.Applet;
import java.util.*;
import java.awt.*;

public class MyTableSetting extends Applet {
    Soup s;                                             // we will show the soup bowl with the soup's alphabet pieces
    int bowlLength = 150;                               // bowl's dimensions as variables in case we want to change it
    int bowlWidth = 220;
    int bowlX = 60;
    int bowlY = 10;

    public void init(){
        setSize(400,200);                                 // make the applet size big enough for our soup bowl
        s = new Soup();                                   // instantiate the Soup
        Producer p1 = new Producer(this, s);              // declare and instantiate one producer thread - state of NEW
        Consumer c1 = new Consumer(this, s);              // declare and instantiate one consumer thread - state of NEW

        p1.start();                                       // start the producer thread
        c1.start();                                       // start the consumer thread
    }

    public void paint(Graphics g){                        // first we make the bowl and spoon
        int x;
        int y;
        g.setColor(Color.orange);
        g.fillOval(bowlX, bowlY, bowlWidth, bowlLength);  // the bowl
        g.setColor(Color.cyan);
        g.fillOval(10, 25, 40, 55);                       // the spoon
        g.fillOval(25, 80, 8, 75);
        g.setColor(Color.black);                          // black outlines for the dinnerware
        g.drawOval(10, 25, 40, 55);
        g.drawOval(25, 80, 8, 75);
        g.drawOval(bowlX,bowlY, bowlWidth, bowlLength);
        ArrayList <String> contents = s.getContents();  // get contents of the soup
        for (String each: contents){                      // individually add each alphabet piece in the soup
            x = bowlX + bowlWidth/4 +(int)(Math.random()* (bowlWidth/2));  // put them at random places to mimic stirring
            y = bowlY + bowlLength/4 + (int)(Math.random()* (bowlLength/2));
            Font bigFont = new Font("Helvetica", Font.BOLD, 20);
            g.setFont(bigFont);
            g.drawString(each, x, y);
        }
    }	
}

Save and run it. Look at the Console and the Soup Bowl. The letters move around because they are being "stirred" as they are eaten.

Run it a few times (be sure to close the applets when you're finished so they don't pile up). If you run the program enough times, eventually you'll have problems:

Play with the sleep frequency in the Producer and Consumer to see how changing that number effects the way the Applet runs.

And, for your added enjoyment, here's another simple illustration of producer/consumer/monitor code at work. Enjoy! And see you in the next lesson...