13: Threads

What We Will Cover


Illuminations

Questions on Completed Assignments?

  • Nothing

13.1: Thread Fundamentals

Objectives

At the end of the lesson the student will be able to:

  • Explain the basic difference between a program that runs in a single thread and a program that runs under multiple threads.
  • Name three common reasons for using threads in a Java application.
  • List the three Java API classes or interfaces that have methods related to threading.
  • Use the Thread class or the Runnable interface to create a thread.
  • Explain the advantage of creating a thread by extending the Runnable interface rather than by inheriting the Thread class.
  • Use the interrupt() method and the InterruptedException class to create a thread that can be interrupted.

13.1.1: About Threads

    Thread: a single sequential flow of control within a program

  • All Java programs run in one or more threads
  • Using threads, you can define tasks that act independently of each other
  • Each task can seemingly operate in parallel
  • A thread scheduler determines which thread to run next
  • Below is an example of threads operating in Java
    • Start each of the applets by clicking on them with the mouse
  • Each sort appears to be operating at the same time because of threads
  • Bubble Sort Bi-Directional
    Bubble Sort
    Quick Sort

  • The program works because the thread scheduler allows each thread to run a short while
  • After a short time, a context switch occurs that changes which thread is running
  • Since context switches occur frequently, it appears that multiple tasks are happening at the same time

Typical Uses for Threads

  • To give the appearance of parallel processing
    • Like that shown above
  • To make use of multiple processors and perform tasks in parallel
    • Multiple-processor chips are now shipping
    • May cause a dramatic shift in the programming paradigm
  • When there are many tasks to perform and one is often waiting
    • For example, I/O

  • To improve the responsiveness of a user interface
    • Allow time-consuming tasks to occur in the background

13.1.2: Classes and Interfaces for Working with Threads

  • There are two main classes and one interface commonly used for working with threads
  • These classes and interfaces are shown in the following table:
  • Class/Interface Description
    Thread A class that defines a thread.
    Runnable An interface that must be implemented by the class of any object that is going to be executed by a thread.
    Object Several methods of the Object class are used for threading.

  • The inheritance relationship of these modules is shown in the following diagram:
  • The Runnable interface has just one method: run()
  • Any thread calls the run() method when it starts running
  • In addition, threads end when they complete the run() method
  • Constructors and methods of the Thread class are shown below
    • We will cover these methods in the following sections
  • In addition, methods of the Object class used for threading are shown
  • These methods are used for signal-wait synchronization, which we cover toward the end of this lesson

Commonly Used Constructors of the Thread Class

Constructor Description
Thread() Creates a new Thread object with default settings.
Thread(Runnable) Creates a new Thread object from any object that implements the Runnable interface
Thread(String) Creates a new Thread object with the specified name.
Thread(Runnable, String) Creates a new Thread object with the specified name from any object that implements the Runnable interface.

Commonly Used Methods of the Thread Class

Method Description
run() Called by the thread scheduler to run the thread. All subclasses of Thread should override this method.
start() Called to start the execution of the thread.
currentThread() A static method that returns a reference to the currently executing thread.
sleep(long millis) A static method that stops the current thread from executing for the specified number of milliseconds.
yield() Causes the currently executing thread object to temporarily pause and allow other threads to execute.
getName() Returns the name of the thread.
getState() Returns the state of the thread.
interrupt() Interrupts this thread.
isInterrupted() Tests whether this thread has been interrupted.
join() Waits for the thread on which it is called to finish and join the calling thread.
setPriority(int) Changes the priority of the thread.

Methods of the Object Class Used for Threading

Method Description
wait() Causes current thread to wait until another thread invokes the notify() method or the notifyAll() method for the current object.
notify() Wakes up a single arbitrary thread that is waiting on this object's monitor.
notifyAll() Wakes up all threads that are waiting on this object's monitor.

13.1.3: Using the sleep() Method

  • When you review the list of methods in the Thread class, you might notice the sleep() method
  • This method simply causes the current from running for a period of time
  • Since it is a static method, we can call it without a reference to the current Thread object
  • However, the method can throw an InterruptedException which we must handle
  • Thus, we could write a simple method to pause the current thread for a number of milliseconds
  • For instance:
  • public void pause(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted!");
            System.exit(1);
        }
    }
    
  • This might be useful for simple animation like that shown in the example below

Example of Simple Animation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

// Unresponsive GUI
public class Bubbles extends JFrame
        implements ActionListener {
    public final static int X_LOC = 100, Y_LOC = 100,
                            WIDTH = 400, HEIGHT = 300;
    public static final int DELAY = 100;
    public static final int COLORS = 16581375;
    public static final int MAX_SIZE = 40;
    public static final int NUM_SHAPES = 400;

    private JButton startButton;
    private JButton stopButton;
    private JPanel canvas;

    public static void main(String[] args) {
        new Bubbles();
    }

    public Bubbles() {
        super("Bubbles Animation");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        Container pane = getContentPane();

        canvas = new JPanel();
        pane.add(canvas, BorderLayout.CENTER);

        startButton = new JButton("Start");
        stopButton = new JButton("Stop");
        JPanel panel = new JPanel();
        panel.add(startButton);
        startButton.addActionListener(this);
        panel.add(stopButton);
        stopButton.addActionListener(this);
        pane.add(panel, BorderLayout.SOUTH);

        setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
        setVisible(true);
    }

    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();
        if (source == startButton) {
            draw();
        } else if (source == stopButton) {
            System.out.println("Goodbye!");
            System.exit(0);
        }
    }

    public void draw() {
        Graphics g = canvas.getGraphics();
        Color c;
        for (int i = 0; i < NUM_SHAPES; i++) {
            int x1 = (int)(Math.random() * WIDTH);
            int y1 = (int)(Math.random() * HEIGHT);
            int size = (int)(Math.random() * MAX_SIZE);
            c = new Color((int)(Math.random() * COLORS));
            g.setColor(c);
            g.drawOval(x1, y1, size, size);
            pause(DELAY);
        }
    }

    public void pause(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted!");
            System.exit(0);
        }
    }
}

13.1.4: Subclassing Class Thread

  • When we run the previous example, we notice that we cannot use any of the controls until the animation is finished
  • To fix this nonresponsive interface, we can use a thread to run the animation in the background
  • This will free up the GUI thread and allow us to use the controls during the animation
  • There are two ways to implement a thread
    • Subclass Thread and override the run method
    • Implement the Runnable interface
  • The most straightforward way to implement threads is to subclass the Thread class
  • We implement the Thread subclass as an inner class to keep our animation class self-contained
  • private class Bubbler extends Thread
  • The Thread class implements the run() method with an empty body
  • public void run() { }
  • Thus any subclass of Thread is expected to override the run() method
  • We put our animation code in the run() method since that is where the thread runs
  • public void run() {
        // animation code
    }
    
  • Note that we do not call run() directly
  • Instead, to start the thread, we call the start() method
  • Bubbler bub = new Bubbler();
    bub.start();
    
  • The JVM calls run() after we call the start() method
    • This allows the JVM to set up the internal data structures needed to handle the thread
  • When start() is called, the thread starts executing independently

Example of Simple Animation Subclassing Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

// Multithreaded GUI
public class Bubbles2 extends JFrame
        implements ActionListener {
    public final static int X_LOC = 100, Y_LOC = 100,
                            WIDTH = 400, HEIGHT = 300;
    public static final int DELAY = 100;
    public static final int COLORS = 16581375;
    public static final int MAX_SIZE = 40;
    public static final int NUM_SHAPES = 400;

    private JButton startButton;
    private JButton stopButton;
    private JPanel canvas;

    public static void main(String[] args) {
        new Bubbles2();
    }

    public Bubbles2() {
        super("Bubbles Animation");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        Container pane = getContentPane();

        canvas = new JPanel();
        pane.add(canvas, BorderLayout.CENTER);

        startButton = new JButton("Start");
        stopButton = new JButton("Stop");
        JPanel panel = new JPanel();
        panel.add(startButton);
        startButton.addActionListener(this);
        panel.add(stopButton);
        stopButton.addActionListener(this);
        pane.add(panel, BorderLayout.SOUTH);

        setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
        setVisible(true);
    }

    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();
        if (source == startButton) {
            Bubbler bub = new Bubbler();
            bub.start();
        } else if (source == stopButton) {
            System.out.println("Goodbye!");
            System.exit(0);
        }
    }

    private class Bubbler extends Thread {

        public void run() {
            Graphics g = canvas.getGraphics();
            Color c;
            for (int i = 0; i < NUM_SHAPES; i++) {
                int x1 = (int)(Math.random() * WIDTH);
                int y1 = (int)(Math.random() * HEIGHT);
                int size = (int)(Math.random() * MAX_SIZE);
                c = new Color((int)(Math.random() * COLORS));
                g.setColor(c);
                g.drawOval(x1, y1, size, size);
                pause(DELAY);
            }
        }

        public void pause(int milliseconds) {
            try {
                Thread.sleep(milliseconds);
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted!");
                System.exit(0);
            }
        }
    }
}

13.1.5: Implementing Interface Runnable

  • There are times when you would rather not make a thread class subclass Thread
  • The alternative is to make your class implement the Runnable interface
  • The Runnable interface has only one method to implement:
  • public void run();
  • A class that implement Runnable must still run from an instance of the class Thread
  • This is usually done by passing the Runnable object as an argument to a thread constructor
  • Here is a template of how this is often done:
  • public class MyClass implements Runnable {
    
    public void run() {
        // code to run like when subclassing Thread
    }
    
        // Somewhere you start the thread
        Thread runner = new Thread(this);
        runner.start();
    }
    
  • We apply this template to our animation in the following example

Example of Simple Animation Implementing Runnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

// Multithreaded runable GUI
public class Bubbles3 extends JFrame
        implements ActionListener, Runnable {
    public final static int X_LOC = 100, Y_LOC = 100,
                            WIDTH = 400, HEIGHT = 300;
    public static final int DELAY = 100;
    public static final int COLORS = 16581375;
    public static final int MAX_SIZE = 40;
    public static final int NUM_SHAPES = 400;

    private JButton startButton;
    private JButton stopButton;
    private JPanel canvas;

    public static void main(String[] args) {
        new Bubbles3();
    }

    public Bubbles3() {
        super("Bubbles Animation");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        Container pane = getContentPane();

        canvas = new JPanel();
        pane.add(canvas, BorderLayout.CENTER);

        startButton = new JButton("Start");
        stopButton = new JButton("Stop");
        JPanel panel = new JPanel();
        panel.add(startButton);
        startButton.addActionListener(this);
        panel.add(stopButton);
        stopButton.addActionListener(this);
        pane.add(panel, BorderLayout.SOUTH);

        setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
        setVisible(true);
    }

    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();
        if (source == startButton) {
            Thread runner = new Thread(this);
            runner.start();
        } else if (source == stopButton) {
            System.out.println("Goodbye!");
            System.exit(0);
        }
    }

    public void run() {
        Graphics g = canvas.getGraphics();
        Color c;
        for (int i = 0; i < NUM_SHAPES; i++) {
            int x1 = (int)(Math.random() * WIDTH);
            int y1 = (int)(Math.random() * HEIGHT);
            int size = (int)(Math.random() * MAX_SIZE);
            c = new Color((int)(Math.random() * COLORS));
            g.setColor(c);
            g.drawOval(x1, y1, size, size);
            pause(DELAY);
        }
    }

    public void pause(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted!");
            System.exit(0);
        }
    }
}

13.1.6: Interrupting Threads

  • Note that a thread ends by finishing its run() method
  • There is no reliable method that another thread can call to stop a thread
  • The correct coding practice is for the thread to check periodically if it should end
  • 
    public void run() {
        while (stop == false) {
            // perform actions repeatedly
        }
    }
    
  • To support this approach, the Thread class has an interrupt() method
  • Calling the interrupt() method either:
    • Sets the status of the thread to interrupted
    • Invokes an exception
  • To check the interrupt status, you use the method isInterrupted()
  • However, if the thread is sleeping (blocked), it cannot execute the code that sets the interrupted status
  • Instead, the thread clears the interrupt status and throws an InterruptedException
  • Thus we must consider both cases and implement code to:
    • Stop a thread while executing
    • Stop a thread if blocked
  • To do this, we create a boolean instance variable named stop
  • private boolean stop;
  • We redefine the Stop button so that whenever it is pressed we set the stop variable to true
  • if (source == stopButton) {
        if (runner != null) {
            stop = true;
            runner.interrupt();
        }
    }
    
  • Since the Stop button can be pressed without pressing the Start button, we protect against this case with an if statement as shown
  • Next we code the run() method to check for a stop condition each time though its loop
  • for (int i = 0; i < NUM_SHAPES && !stop; i++)
    
  • In addition to these exceptions, there are others that may be thrown.
  • For our simple application, however, we will let these exceptions be caught by the general exception handler
  • The complete example is shown below

Example of Simple Animation With Stop Enabled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

// Stopable GUI
public class Bubbles4 extends JFrame
        implements ActionListener, Runnable {
    public final static int X_LOC = 100, Y_LOC = 100,
                            WIDTH = 400, HEIGHT = 300;
    public static final int DELAY = 100;
    public static final int COLORS = 16581375;
    public static final int MAX_SIZE = 40;
    public static final int NUM_SHAPES = 400;

    private JButton startButton;
    private JButton stopButton;
    private JPanel canvas;
    private Thread runner;
    private boolean stop;

    public static void main(String[] args) {
        new Bubbles4();
    }

    public Bubbles4() {
        super("Bubbles Animation");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        Container pane = getContentPane();

        canvas = new JPanel();
        pane.add(canvas, BorderLayout.CENTER);

        startButton = new JButton("Start");
        stopButton = new JButton("Stop");
        JPanel panel = new JPanel();
        panel.add(startButton);
        startButton.addActionListener(this);
        panel.add(stopButton);
        stopButton.addActionListener(this);
        pane.add(panel, BorderLayout.SOUTH);

        setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
        setVisible(true);
    }

    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();
        if (source == startButton) {
            stop = false;
            runner = new Thread(this);
            runner.start();
        } else if (source == stopButton) {
            if (runner != null) {
                stop = true;
                runner.interrupt();
            }
       }
    }

    public void run() {
        Graphics g = canvas.getGraphics();
        Color c;
        for (int i = 0; i < NUM_SHAPES && !stop; i++) {
            int x1 = (int)(Math.random() * WIDTH);
            int y1 = (int)(Math.random() * HEIGHT);
            int size = (int)(Math.random() * MAX_SIZE);
            c = new Color((int)(Math.random() * COLORS));
            g.setColor(c);
            g.drawOval(x1, y1, size, size);
            pause(DELAY);
        }
    }

    public void pause(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted!");
        }
    }
}

More Information

13.1.7: Summary

  • A thread is a single flow of control through a program
  • Java allows the programmer to write programs with multiple threads
  • Using threads, you can define tasks that act independently of each other
  • A thread scheduler determines which thread to run next
  • Multithreading is typically used to:
    • To give the appearance of parallel processing
    • To make use of multiple processors and perform tasks in parallel
    • When there are many tasks to perform and one is often waiting
    • To improve the responsiveness of a user interface
  • You can create a thread by either:
    • Extending the Thread class and instantiating the new class
    • Implementing the Runnable interface and passing a reference of the object to a constructor of the Thread class
  • To start a thread, you call the start() method of the Thread class
    • You never call the run() method directly
  • Stopping a thread is more difficult than starting one
  • The way to end a thread is to finish its run() method
  • Each thread is responsible for checking itself periodically to see if it should end
  • You can use methods interrupt() and isInterrupted() to help with this task
  • However, if a thread is blocked, it cannot execute the code that sets the interrupted status
  • Instead, the thread clears the interrupt status and throws an InterruptedException
  • Thus, you must write code that works both if the thread is running and if the thread is blocked

Exercise 13.1

Take one minute to prepare an answer to the following questions

  1. What is a thread?
  2. What are three typical reasons for using threads? (answer)
  3. What two classes and one interface do you use to work with threads?
  4. What two techniques can you use to create a thread?

13.2: Using Timers

Objectives

At the end of the lesson the student will be able to:

  • Explain the task of the event thread
  • Describe when to use the timer classes
  • Use the Timer class of the javax.swing package to schedule tasks.
  • Use the Timer and TimerTask classes of the java.util package to schedule tasks.

13.2.1: Implicit Event Thread

  • Every GUI program has two threads
  • The main thread is the one executing the main() method
  • The other thread is called the event-dispatching thread, or just event thread
  • The event thread detects GUI events and invokes methods like paintComponent() and various listener methods like actionPerformed()
  • The main thread often terminates after launching the GUI
  • To show that two threads are operating, we can put the main thread in a loop
  • while (true) { // main thread never ends
        count++;
        Thread.currentThread().sleep(ONE_SECOND);
        System.out.println("Count = " + count);
    }
    
  • Before this loop, we can create a GUI which responds to our controls even when the main thread is sleeping
  • The code is shown below

Example Program Showing the Event Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class EventThread {
    public static final int ONE_SECOND = 1000;

    public static void main(String[] args)
            throws InterruptedException {
        createGUI();
        int count = 0;
        while (true) { // main thread never ends
            count++;
            Thread.currentThread().sleep(ONE_SECOND);
            System.out.println("Count = " + count);
        }
    }

    public static void createGUI() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(
            JFrame.EXIT_ON_CLOSE );
        Container pane = frame.getContentPane();
        JButton counter =
            new JButton("Click Me!");
        counter.addActionListener(new ClickCounter());
        pane.add(counter, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }
}

class ClickCounter implements ActionListener {
    private int count = 0;

    public void actionPerformed(ActionEvent e) {
        count++;
        System.out.println("Total clicks = " + count);
    }
}

13.2.2: Using Swing Timers

  • Normally, you should not use threads when working with Swing components
  • This is because Swing components are not generally thread safe
  • This means that if multiple threads manipulate a Swing component, the results may not be correct
  • Instead, most access to Swing components should be done through the event thread
    • Some exceptions are the repaint() and addXXXListener() methods
  • However, sometimes you need to perform some task repeatedly in Swing -- like animation
  • To make this easy, Java has the javax.swing.Timer class
  • The Swing Timer class works by firing periodic action events
  • Thus, using a Swing timer is like working with Swing event handlers
  • Constructors and methods of the Swing Timer class are shown in the table below

Commonly-Used Constructors and Methods from javax.swing.Timer

Constructor/Method Description
Timer(intMillis, ActionListener) Creates a new Timer object.
start() Starts the Timer sending action events to its listeners.
stop() Stops the Timer.
setDelay(intMillis) Sets the number of milliseconds between successive action events.
setRepeats(boolean) If set to false , the Timer only sends one action event to its listeners.

More Information

13.2.3: Example Using Swing Timers

  • To implement the timer you first need to code a class to receive action events
  • public class Bubbles5 extends JFrame
            implements ActionListener {
    
  • Then we code some methods to control the animation
  • public void initAnimation() {
        timer = new javax.swing.Timer(DELAY, this);
        timer.setInitialDelay(0);
        timer.setCoalesce(true);
    }
    
    public void startAnimation() {
        timer.start();
    }
    
    public void stopAnimation() {
        timer.stop();
    }
    
  • And finally we code and action event handler for the timer
  • if (source == timer) {
        if (numShapes < NUM_SHAPES) {
            drawBubble();
            numShapes++;
        } else {
            stopAnimation();
        }
    }
    
  • An complete example program using a Swing timer is shown below:

Example Using a Swing Timer for Animation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

// Using Swing Timers
public class Bubbles5 extends JFrame
        implements ActionListener {
    public final static int X_LOC = 100, Y_LOC = 100,
                            WIDTH = 400, HEIGHT = 300;
    public static final int DELAY = 100;
    public static final int COLORS = 16581375;
    public static final int MAX_SIZE = 40;
    public static final int NUM_SHAPES = 400;

    private JButton startButton;
    private JButton stopButton;
    private JPanel canvas;
    private Thread runner;
    private boolean stop;

    // For animation
    private javax.swing.Timer timer;
    private int numShapes;

    public static void main(String[] args) {
        new Bubbles5();
    }

    public Bubbles5() {
        super("Bubbles Animation");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        Container pane = getContentPane();

        canvas = new JPanel();
        pane.add(canvas, BorderLayout.CENTER);

        startButton = new JButton("Start");
        stopButton = new JButton("Stop");
        JPanel panel = new JPanel();
        panel.add(startButton);
        startButton.addActionListener(this);
        panel.add(stopButton);
        stopButton.addActionListener(this);
        pane.add(panel, BorderLayout.SOUTH);

        initAnimation();

        setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
        setVisible(true);
    }

    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();
        if (source == startButton) {
            startAnimation();
        } else if (source == stopButton) {
            stopAnimation();
        } else if (source == timer) {
            if (numShapes < NUM_SHAPES) {
                drawBubble();
                numShapes++;
            } else {
                stopAnimation();
            }
        }
    }

    public void drawBubble() {
        Graphics g = canvas.getGraphics();
        Color c;
        int x1 = (int)(Math.random() * WIDTH);
        int y1 = (int)(Math.random() * HEIGHT);
        int size = (int)(Math.random() * MAX_SIZE);
        c = new Color((int)(Math.random() * COLORS));
        g.setColor(c);
        g.drawOval(x1, y1, size, size);
    }

    public void initAnimation() {
        timer = new javax.swing.Timer(DELAY, this);
        timer.setInitialDelay(0);
        timer.setCoalesce(true);
    }

    public void startAnimation() {
        timer.start();
    }

    public void stopAnimation() {
        timer.stop();
    }
}

13.2.4: Using Utility Timers

  • The javax.swing.Timer class relies on the event thread
    • And therefore a GUI
  • An approach that does not need the event thread is use the Timer and TimerTask classes of the java.util package to schedule tasks
  • A Timer object can schedule one or more TimerTask objects to perform actions once or repeatedly
  • To use the utility timer:
    1. Code a class that extends the TimerTask and override its run() method
    2. class Task extends TimerTask {
          public void run() {
              // code to perform the task
          }
      }
      
    3. Create an object from the Timer class
    4. Timer timer = new Timer();
    5. Use the schedule() method of the Timer object to call TimerTask objects at specified times or intervals
    6. timer.schedule(new Task(), seconds);
  • Commonly used constructors and methods of TimerTask and Timer are shown below

Commonly-Used Constructors and Methods from TimerTask

Constructor/Method Description
TimerTask() Creates a new TimerTask object.
run() Must be overridden to handle specific actions for this timer task.

Commonly-Used Constructors and Methods from Timer

Constructor/Method Description
Timer() Creates a new Timer object that runs in a user thread.
Timer(boolean) If set to true, creates a new Timer object that runs in a daemon thread.
schedule(TimerTask, Date) Schedules the specified TimerTask for execution at the specified Date.
schedule(TimerTask, long) Schedules the specified TimerTask for execution at the specified delay in milliseconds.
scheduleAtFixedRate(TimerTask, Date, long) Schedules the specified TimerTask to start executing at the specified Date and to repeat after each long delay.
scheduleAtFixedRate(TimerTask, long, long) Schedules the specified TimerTask to start executing after the first long delay and to repeat after each long delay.
cancel() Terminates this Timer, canceling any timer tasks.

Further Information

13.2.5: Example Using the Utility Timer

  • You can use the Timer class to schedule a task for execution in many ways
  • For instance, can schedule a task to be performed after a delay
  • The schedule method of Timer is overloaded to allow scheduling tasks in a number of ways
  • For instance, you can schedule for a specific point in time using a Date object
  • //Get the Date corresponding to 9:01:00 PM today.
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 21);
    calendar.set(Calendar.MINUTE, 1);
    calendar.set(Calendar.SECOND, 0);
    Date time = calendar.getTime();
    
    timer = new Timer();
    timer.schedule(new Task(), time);
    
  • Also, you can schedule tasks to repeat at fixed intervals
  • const int initialDelay = 0;
    const int fixedRate = 1000; // in milliseconds
    timer = new Timer();
    timer.schedule(new Task(), initialDelay, fixedRate);
    
  • Following program demonstrates using a utility timer in a simple way

Example Using a Utility Timer to Schedule Tasks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.*;
import javax.swing.JOptionPane;

public class Scheduler {
    public static void main(String args[]) {
        System.out.println("Scheduling task");
        new Scheduler(5);
        System.out.println("Task scheduled");
    }

    public Scheduler(int seconds) {
        seconds *= 1000;
        Timer timer = new Timer();
        timer.schedule(new Task(), seconds);
    }

    private class Task extends TimerTask {
        public void run() {
            JOptionPane.showMessageDialog(null,
                "Time's up!");
            System.exit(0);
        }
    }
}

Example Using a Utility Timer to Schedule an Alarm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.*;
import javax.swing.JOptionPane;

public class Alarm {
    private java.util.Timer timer;

    public static void main(String[] args) {
        System.out.println("Scheduling alarm");
        Alarm alarm = new Alarm();
        System.out.println("Alarm scheduled");
    }

    public Alarm() {
        GregorianCalendar dateTime =
            new GregorianCalendar(2005,
                Calendar.JUNE, 3, 17, 0);
        Date alarmDateTime = dateTime.getTime();
        timer = new Timer();
        timer.schedule(new AlarmTask(), alarmDateTime);
    }
}

class AlarmTask extends TimerTask {
    public void run() {
        JOptionPane.showMessageDialog(null,
            "Schools out!");
        System.exit(0);
    }
}

13.2.6: Summary

  • Every GUI program runs a second thread called the event thread
  • The event thread is responsible for keeping the GUI up to date
  • For example, the thread schedules repainting, fires events and runs event listener code
  • Swing components should only be run in the event thread
  • To allow you to run repeated tasks on Swing components, like animation, you can use the javax.swing.Timer class
  • The Swing timer will periodically generate an ActionEvent
  • Then you can write action event listener code to handle the periodic tasks
  • Java also supplies another general purpose time called the utility timer
  • This timer uses its own thread and does not rely on the event thread
  • The utility timer is more versatile than the Swing timer
  • To use the utility timer:
  1. Code a class that extends the TimerTask and override its run() method
  2. class Task extends TimerTask {
        public void run() {
            // code to perform the task
        }
    }
    
  3. Create an object from the Timer class
  4. Timer timer = new Timer();
  5. Use the schedule() method of the Timer object to call TimerTask objects at specified times or intervals
  6. timer.schedule(new Task(), seconds);

Exercise 13.2

Take one minute to prepare an answer to the following questions:

  1. How many threads run, at a minimum, for every GUI program?
  2. What are the steps to create a utility timer?
  3. Which of the following methods would we use to schedule a task for execution once a minute starting at 9:00 PM tonight?
    1. schedule(TimerTask task, long delay, long period)
    2. schedule(TimerTask task, Date firstTime, long period)
    3. scheduleAtFixedRate(TimerTask task, long delay, long period)
    4. scheduleAtFixedRate(TimerTask task, Date firstTime, long period)

13.3: Using Threads Cooperatively

Objectives

At the end of the lesson the student will be able to:

  • List the six states of a thread, and describe the status of a thread in each state.
  • Explain the difference between the sleep() and yield() methods
  • Set the priority of threads
  • Describe how to run threads cooperatively

13.3.1: More About Threads

  • So far we have been using only two threads: the main thread and one child thread
  • However, our programs can contain potentially many threads
  • When this occurs we need to consider issues such as
    • When does each thread run?
    • How do we control the execution?
    • In what state is the thread?
  • To answer these questions, we need to better understand how threads operate

Life Cycle of a Thread

  • Running a thread is different than executing other program code
  • Threads have a definite starting point and a definite end point
  • Over their lifetime, they can exist in one of several states
  • These states are shown in the following diagram (from The Life Cycle of a Thread)
  • Note that this is not a complete state diagram but an overview of a thread's life cycle
  • "Not Runnable" can be one of three different states:
    • Blocked
    • Waiting
    • Timed Waiting
  • Also notice that there is no way to tell the thread scheduler when to run a particular thread
  • We can put a thread into a wait state
  • In addition, we can give the thread scheduler hints about what thread to run
  • However, there is no way to direct the thread scheduler to run one particular thread out of many
  • This lets the thread scheduler keep threads running even if one gets blocked by I/O or other causes

Thread States

State Description
New The threads has been created by calling it's constructor, but has not yet started running.
Runnable The thread's start methods has been called and the thread is available to the thread scheduler. The thread may be running or may be waiting in a queue for an opportunity to run.
Blocked The thread has been temporarily removed from the Runnable state waiting for a monitor lock.
Waiting The thread is waiting for another thread to perform a task. This state can be entered through calls to Object.wait() or Thread.join().
Timed Waiting The thread is waiting with a specified waiting time for another thread to perform a task.
Terminated The thread's run method has ended.

13.3.2: Example of Multiple Threads

  • When a program starts, one thread starts running immediately: the main thread
  • All other threads are spawned from the main thread
  • In the following example, you can see two threads spawned from the main thread
  • Inside each thread, notice the use of the yield() method
  • Method yield() pauses currently executing thread object and allows other threads to execute
  • This makes sure that each thread gets a chance to run

Example of Multiple Threads

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class CountDownApp {
    public static void main(String[] args) {
        Thread t0 = new CountDownEven();
        Thread t1 = new CountDownOdd();
        t0.start();
        t1.start();
        System.out.println("Main thread done");
    }
}

class CountDownEven extends Thread {
    public void run() {
        for (int i = 10; i > 0; i-= 2) {
            System.out.println(getName()
                + " Count: " + i);
            Thread.yield();
        }
    }
}

class CountDownOdd extends Thread {
    public void run() {
        for (int i = 9; i > 0; i-= 2) {
            System.out.println(getName()
                + " Count: " + i);
            Thread.yield();
        }
    }
}

13.3.3: Using Methods getState() and join()

  • Often you will want one thread to finish before another
  • To make one thread wait for another to finish, you use the join() method
  • The name of the join() method comes from the idea of one thread waiting until another thread "joins it"
  • Another method that is often useful is getState()
    • Introduced in JDK 1.5
  • Method getState() returns one of the six states listed in the beginning of this section
  • The following code shows a simple example using both of these methods
  • The main() method waits to exit until other threads have finished
    • Unlike most previous examples
  • In addition, we use the getState() method to display the state of the threads during their life cycle

Example Using getState() and join()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class CountDownJoinApp {
    public static void main(String[] args)
            throws InterruptedException {
        Thread t0 = new CountDownEven();
        Thread t1 = new CountDownOdd();
        System.out.println(t0.getName()
            + " " + t0.getState());
        System.out.println(t1.getName()
            + " " + t1.getState());

        t0.start();
        t1.start();
        System.out.println(t0.getName()
            + " " + t0.getState());
        System.out.println(t1.getName()
            + " " + t1.getState());

        t0.join();
        t1.join();
        System.out.println(t0.getName()
            + " " + t0.getState());
        System.out.println(t1.getName()
            + " " + t1.getState());
        System.out.println("Main thread done");
    }
}

class CountDownEven extends Thread {
    public void run() {
        for (int i = 10; i > 0; i-= 2) {
            System.out.println(getName()
                + " Count: " + i);
            Thread.yield();
        }
    }
}

class CountDownOdd extends Thread {
    public void run() {
        for (int i = 9; i > 0; i-= 2) {
            System.out.println(getName()
                + " Count: " + i);
            Thread.yield();
        }
    }
}

13.3.4: Thread Priorities and Scheduling

  • Each thread has a priority that helps the thread scheduler decide when to run a thread
  • Threads are selected to run based on their priority relative to other Runnable threads
  • The JVM system chooses a runnable thread with the highest priority to run
  • Only when all higher priority threads stop, yield, or becomes not runnable for some reason will a lower priority thread start executing
  • Three constants for thread priorities:
    • Thread.MIN_PRIORITY
    • Thread.NORM_PRIORITY
    • Thread.MAX_PRIORITY
  • Java thread priorities can range from 1-10
  • Any thread currently running will continue until one of the following happens
    • A higher priority thread becomes runnable
    • Its run() method exits
    • It yields
    • On systems that support time-slicing, its time allotment expires
  • The job of the scheduler is to keep the highest priority thread running
  • If time-slicing is available, the scheduler keeps equally high-priority threads running in round-robin fashion
  • Note that threads on different systems can have very different behavior
  • Most inconsistencies occur when threads rely on preemptive behavior
  • The safest way to get predictable, cross-platform results is to use threads that voluntarily relinquish control of the CPU

Example of Threads with Different Priorities

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class CountDownPrioritiesApp {
    public static void main(String[] args) {
        Thread t0 = new CountDownEven();
        Thread t1 = new CountDownOdd();
        t0.setPriority(Thread.MIN_PRIORITY);
        t1.setPriority(Thread.MAX_PRIORITY);
        t0.start();
        t1.start();
        System.out.println("Main thread done");
    }
}

class CountDownEven extends Thread {
    public void run() {
        for (int i = 10; i > 0; i-= 2) {
            System.out.println(getName()
                + " Count: " + i);
            Thread.yield();
        }
    }
}

class CountDownOdd extends Thread {
    public void run() {
        for (int i = 9; i > 0; i-= 2) {
            System.out.println(getName()
                + " Count: " + i);
            Thread.yield();
        }
    }
}

13.3.5: Thread Pooling

  • Thread Pool: a managed collection of threads for performing tasks
  • Thread pools are commonly used in multithreaded applications because:
    • The overhead of creating and destroying threads is reduced
    • The system resources required for the threads is prevented from growing too large
    • It makes managing threads easier for the programmer
  • JDK 1.5 introduced the concurrency API which includes classes and interfaces for thread pools
  • To use the API you need to import the java.util.concurrent package
  • import java.util.concurrent.*
  • To create a thread pool, you can use a factory method of the Executors class
  • Thread pools in the API implement the ExecutorService interface
  • Thus, a thread pool is constructed using code like:
  • ExecutorService tp =
        Executors.newFixedThreadPool(2);
    
  • Several types of thread pools are available as shown in the table below
  • One a thread pool is constructed, you add "worker" threads to perform the tasks using the execute() method
  • Thread t0 = new CountDownEven();
    tp.execute(t0);
    
  • The execute() method starts the thread which in turn calls the run() method
  • To stop all the threads in the pool, you use the shutdown() method
  • tp.shutdown();
  • The thread pool stops accepting new threads and eventually all the threads complete and the service shuts down
  • Note that in the example below, you can substitute any of the factory methods and see the different behavior for the type of thread pool

Factory Methods in the Executors Class

Factory Method Description
newFixedThreadPool() Creates a thread pool with a fixed size.
newCachedThreadPool() Creates a thread pool that reclaims threads but with no upper limit on the number of threads.
newSingleThreadExecutor() Creates a thread pool with a single "worker" thread

Example Using a Fixed-Size Thread Pool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.concurrent.*;

public class CountDownPoolApp {
    public static void main(String[] args) {
        ExecutorService tp =
            Executors.newFixedThreadPool(2);

        Thread t0 = new CountDownEven();
        Thread t1 = new CountDownOdd();
        Thread t2 = new CountDownEven();
        Thread t3 = new CountDownOdd();

        tp.execute(t0);
        tp.execute(t1);
        tp.execute(t2);
        tp.execute(t3);
        tp.shutdown();
        System.out.println("Main thread done");
    }
}

class CountDownEven extends Thread {
    public void run() {
        for (int i = 10; i > 0; i-= 2) {
            System.out.println(getName()
                + " Count: " + i);
            Thread.yield();
        }
    }
}

class CountDownOdd extends Thread {
    public void run() {
        for (int i = 9; i > 0; i-= 2) {
            System.out.println(getName()
                + " Count: " + i);
            Thread.yield();
        }
    }
}

13.3.6: Summary

  • In this section we look at how to make multiple asynchronous threads work together well
  • We began by looking at the six states of a thread shown in the Enum Thread.State
  • Method getState() returns one of the six values
  • Grouping all the Not Runnable states together, we have a diagram like this:
  • You can usually get threads to work together well by making sure they surrender control of the CPU
  • Thread.yield();
  • One of the methods to accomplish this is the yield() method
  • Method yield() pauses the currently executing thread and allows another threads to execute
  • The thread scheduler is responsible for deciding which thread to run and for how long
  • You can influence which threads are run by setting their priority
  • However, you cannot explicitly control which thread is run
  • If you want one thread to finish before another you can use the join() method
  • One way to reduce the complexity of managing threads is to use thread pooling
  • JDK 1.5 introduced the concurrency API which includes classes and interfaces for thread pools
  • We looked at an example of a thread pool constructed from the API

Exercise 13.3

Take one minute to prepare an answer to the following questions:

  1. What state must a thread be in for the thread scheduler to run it?
  2. What is the difference between the sleep() and yield() methods
  3. How do you set the priority of threads?
  4. How do you make sure threads run cooperatively on multiple platforms?

13.4: Communicating Between Threads

Objectives

At the end of the lesson the student will be able to:

  • Use the synchronized keyword to control access to critical sections.
  • Use the wait() and notifyAll() methods of the Object class to coordinate the execution of two interdependent threads.
  • Describe the producer/consumer pattern used for concurrency control.

13.4.1: About Thread Communication

  • So far our threads have run at their own pace without concern for the state or activities of other threads
  • These types of threads are known as asynchronous threads
  • However, sometimes we need threads to share data
  • For example:
    • Some threads take orders and some threads process orders
    • Some threads put money in accounts and some get money from accounts
    • Some threads put keystrokes into a buffer and some get keystrokes from a buffer
  • This is known as the producer-consumer pattern
  • Within a single JVM, it is common to use shared objects for communication between threads
  • Some definitions:
    • Buffer: the shared object that stores data
    • Producer: the thread that puts data into the buffer
    • Consumer: the thread that get data from the buffer

13.4.2: Example: Producer-Consumer Application

  • Let us construct a producer-consumer setup using a shared object
  • To keep the example simple, we use an object that stores int data
  • Data is stored in the variable:
  • private int amount;
  • We provide access to the variable using a add() and get() methods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Buffer {
    private int amount;

    public void add(int value) {
        System.out.print("Adding " + value);
        int newAmount = amount + value;
        System.out.println(", amount=" + newAmount);
        amount = newAmount;
    }

    public void get(int value) {
        System.out.print("Getting " + value);
        int newAmount = amount - value;
        System.out.println(", amount=" + newAmount);
        amount = newAmount;
    }
}
  • To put int values into the buffer, we code a Producer class
  • Each Producer thread just adds the value ten times
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Producer extends Thread {
    private Buffer buf;
    private int value;

    public Producer(Buffer buffer, int newValue) {
        buf = buffer;
        value = newValue;
    }

    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                buf.add(value);
                sleep(10);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • To remove items from the buffer, we code a Consumer class
  • Each Consumer thread just removes the value ten times
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Consumer extends Thread {
    private Buffer buf;
    private int value;

    public Consumer(Buffer buffer, int newValue) {
        buf = buffer;
        value = newValue;
    }

    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                buf.get(value);
                sleep(10);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • To manage the application, we code a ProducerConsumerApp class
  • The application first creates the shared buffer object
  • Then it creates producer and consumer threads and calls their start() method
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ProducerConsumerApp {
    public static void main(String[] args) {
        Buffer buf = new Buffer();
        Thread t0 = new Producer(buf, 12);
        Thread t1 = new Consumer(buf, 12);
        Thread t2 = new Producer(buf, 12);
        Thread t3 = new Consumer(buf, 12);
        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}
  • What we would expect by running this application is for the amount to vary as values are added and removed
  • However, in the end the amount in the buffer should be zero
  • Instead, when you run this program repeatedly, you will notice that output is messed up and the final amount is not zero
  • Clearly, something incorrect is happening

13.4.3: Critical Sections

  • Critical Section: a piece of code that can only be executed by one thread (or process) at a time
  • Sections of code where resources are shared need to be critical sections
  • The reason is that a context switch occurs when the program changes which thread is running
  • Even the shortest of Java statements have many steps.
  • For instance, the Java statement: i++
  • Compiles to bytecode like:
  • load value
    inc
    store value
    
  • What if a context switch occurs in the middle of this sequence?
  • Thread 1      Thread 2      Value
    load value                    0
                  load value
                  inc
                  store value     1
    
                  load value
                  inc
                  store value     2
    
    inc
    store value                   1 (corruption!)
    
  • This is known as a race condition
  • In the next section we correct the race conditions in our code

13.4.4: Creating synchronized Threads

  • To prevent data corruption due to race conditions, you must allow only one thread at a time to add or get values
  • In other words, we must turn the add() and get() methods into critical sections
  • To accomplish this in Java, you can use the synchronized keyword in the method declaration
  • public synchronized void myMethod() {
        // statements to execute
    }
    
  • Thus, to fix our Buffer class we can add the synchronized keyword to both methods:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Buffer {
    private int amount;

    public synchronized void add(int value) {
        System.out.print("Adding " + value);
        int newAmount = amount + value;
        System.out.println(", amount=" + newAmount);
        amount = newAmount;
    }

    public synchronized void get(int value) {
        System.out.print("Getting " + value);
        int newAmount = amount - value;
        System.out.println(", amount=" + newAmount);
        amount = newAmount;
    }
}

How Synchronization Works

  • Every Java object has a mechanism called a lock
  • The synchronized keyword works by giving the lock from an object to a thread
  • The first thread to access the method acquires the lock
  • When the thread holding the lock finishes the method, the lock is released
  • Any other thread attempting to use the method is blocked until the lock is released
  • Note that there is only one lock per object
  • Thus a lock prevents threads that do not hold the lock from using any synchronized method in the entire object

Synchronizing on Blocks

  • Another way to turn a piece of code into a critical section is to synchronize on a block
  • Syntax:
  • synchronized (objectReference) {
        // code block
    }
    
  • Synchronized blocks can use the lock of any object
  • Thus we can lock the current object using the this keyword:
  • synchronized (this) {
        // code block
    }
    
  • So another way to fix our Buffer class is with code like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Buffer {
    private int amount;

    public void add(int value) {
        synchronized (this) {
            System.out.print("Adding " + value);
            int newAmount = amount + value;
            System.out.println(", amount=" + newAmount);
            amount = newAmount;
        }
    }

    public void get(int value) {
        synchronized (this) {
            System.out.print("Getting " + value);
            int newAmount = amount - value;
            System.out.println(", amount=" + newAmount);
            amount = newAmount;
        }
    }
}

13.4.5: Avoiding Deadlock

  • Deadlock: A situation where two or more threads are unable to continue because each is waiting for another to do something.
  • Lets revisit our producer and consumer activities
  • The producer fills the buffer while consumer empties the buffer

  • Two problems can arise:
    1. The producer may try to add to full buffer
    2. The consumer may try to consume from an empty buffer
  • Suppose that we want to disallow the buffer to go negative
  • We might be tempted to add code to our Buffer class like:
  • public synchronized void get(int value)
            throws InterruptedException {
        while ((amount - value) < 0) {
            Thread.sleep(100);
        }
        System.out.print("Getting " + value);
        int newAmount = amount - value;
        System.out.println(", amount=" + newAmount);
        amount = newAmount;
    }
    
  • The problem with sleep() is that it does not release the lock
  • Now no other method can enter the buffer object and add to the amount
  • But the get() method is waiting for another thread to add to the amount
  • This is an example of deadlock
  • Thus we need some other technique to prevent deadlock while still solving our producer-consumer coordination problems

13.4.6: Using Signal-Wait Synchronization

  • To fix the coordination problems, we use signal-wait synchronization
  • What we want is to stop the producer from producing if the buffer is full
  • Similarly, the consumer must stop consuming if the buffer is empty
  • To implement signal-wait synchronization we use the methods of the Object class shown in the table below
  • Method wait() will put the current thread in a wait state
  • Method wait() releases the lock, unlike method sleep(), so that other threads can execute synchronized methods
  • The effect of the wait() method is to pause the current thread until it gets woken up
  • To wake up a thread in a Waiting state you use either method notify() or notifyAll()
  • Usually, you use the notifyAll() so that an unusable thread is not chosen by the thread scheduler
  • That is also the reason that you usually place the wait() method in a loop
  • while (condition) {
        wait();
    }
    
  • Since notifyAll() returns all threads to the Runnable state, each thread must rerun the test condition to determine if it should proceed
  • If the condition is not correct for the thread, then it should wait again
  • Note that the wait(), notify() and notifyAll() methods can only be used in synchronized methods
  • If you call one from an unsynchronized method, you get an IllegalMonitorStateException
  • Using wait() and notifyAll() does not solve all deadlock problems
  • There is no known general method to avoid all deadlock situations
  • Instead, programmers must take care to rigorously prove their threads cannot deadlock

Methods of the Object Class Used for Threading

Method Description
wait() Causes current thread to wait until another thread invokes the notify() method or the notifyAll() method for the current object.
notify() Wakes up a single arbitrary thread that is waiting on this object's monitor.
notifyAll() Wakes up all threads that are waiting on this object's monitor.

13.4.7: Example of Signal-Wait Synchronization

  • In our Buffer class, method get() must call wait() when it is empty
  • while ((amount - value) < 0) {
        wait();
    }
    
  • Then the method add() must call notifyAll() when something is added to the buffer
  • notifyAll();
  • This wakes up all the waiting threads so that they can try again
  • Similarly, method add() must call wait() when it is full
  • while ((value + amount) > MAX) {
        wait();
    }
    
  • Then method get() must call notifyAll() when it removes something from the buffer
  • notifyAll();
  • This wakes up all the waiting threads so that they can try again
  • The updated Buffer class is shown below

Example Buffer Implementing Signal-Wait Synchronization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Buffer {
    public static final int MAX = 12;
    private int amount;

    public synchronized void add(int value)
            throws InterruptedException {
        while ((value + amount) > MAX) {
            wait();
        }
        System.out.print("Adding " + value);
        int newAmount = amount + value;
        System.out.println(", amount=" + newAmount);
        amount = newAmount;
        notifyAll();
    }

    public synchronized void get(int value)
            throws InterruptedException {
        while ((amount - value) < 0) {
            wait();
        }
        System.out.print("Getting " + value);
        int newAmount = amount - value;
        System.out.println(", amount=" + newAmount);
        amount = newAmount;
        notifyAll();
    }
}

Common Problem

  • It is intuitively clear when to call wait()
  • If a thread cannot do its job then it needs to wait
  • But when a thread calls wait() it just goes into a Waiting state and stops trying
  • The only way to make the thread runnable again is if another thread calls notifyAll() (or notify())
  • A common error is to have threads wait without matching calls to notifyAll() by another thread
  • Whenever you use wait(), ask yourself which call to notifyAll() will notify your thread to try again

13.4.8: Summary

  • Sometimes threads need to communicate with each other
  • One common way to communicate is by using a shared object
  • The producer-consumer pattern is one way for threads to successfully use a shared object
  • When resources are shared, some sections of code need to become critical section
  • Synchronized methods can be used to ensure that two threads do not run the same method of an object simultaneously
  • When a thread calls a synchronized method, the object that contains that method is locked so that it cannot be accessed by another thread
  • When threads coordinate with each other, they may get into undesirable states like deadlock
  • Deadlock results where two or more threads are unable to continue because each is waiting for another to do something
  • You can solve some deadlock problems using the wait() and notifyAll() methods of Object
  • There is no known general method to avoid all deadlock situations
  • Instead, programmers must take care to rigorously prove their threads cannot deadlock

Exercise 13.4

Take one minute to prepare an answer to the following question:

  1. A lock is associated with of the following?
    1. A thread
    2. An object
    3. A method
    4. A variable
  2. Why should you generally use:
  3. while (condition) {
        wait();
    }
    

    rather than
    if (condition) {
        wait();
    }
    

Wrap Up

Home | WebCT | Announcements | Schedule | Room Policies | Course Info
Help | FAQ's | HowTo's | Links

Last Updated: May 21 2005 @20:53:37