Multi-Threading : Doing Things in Parallel

Steven Zeil

Last modified: Mar 22, 2014

Contents:
1. Overview
1.1 Fundamental Ideas
1.2 Parallel versus Concurrent
2. Spawning Processes
2.1 Processes in Unix - Fork
2.2 Heavy & Light Weight Processes
3. Java Threads
3.1 Taking the Cheap Way Out
3.2 Working with Threads
4. Interleaving & Synchronization
4.1 Safety and Parallel Programs
4.2 Synchronization
5. Liveness Properties
5.1 Deadlock
5.2 Livelock
5.3 Fairness
6. Safety
6.1 Mutual Exclusion Mechanisms
7. Direct Control of Thread State
8. Summing Up

Parallelism - Motivation

1. Overview


Concurrency

1.1 Fundamental Ideas


Definitions


Scheduler

A scheduler is


Running Processes

A process that is running may

Blocked Processes

A process that is blocked on some resource

Processes can Interact

Communication between process may be via


Synchronization

Synchronization

1.2 Parallel versus Concurrent

Concurrent software can run in many hardware configurations.


Single Processor


Multi-Processor


Distributed Processors


Looking Ahead

2. Spawning Processes

How do we get multiple processes in one program?

2.1 Processes in Unix - Fork


Concurrency in C++/Unix

Languages like C and C++ lack built-in support for concurrency.


Unix Model

Unix employs a distributed process model.

Process Control

Basic operations:


Example: Forking a Process

 int status;
 if (fork() == 0) {
    /* child 1 */
     execl("./prog1", "prog1", 0);
 } else if (fork() == 0) {
    /* child 2 */
    execl("./prog2", "prog2", 0);
 } else {
    /* parent */
    wait(&status);
    wait(&status);
 }

This program forks into two processes. The child process runs a program prog1, then finishes. The parent moves into the first else, then forks again into two processes. The second child runs a program prog2, then finishes. The parent moves to the final else, where it waits for the two children to finish.


Example: Web-Launched Grading

 close STDOUT;
 close STDERR;
 $pid = fork();
 if (!defined($pid)) {
      die ("Cannot fork: $!\n");
 }
 
 if ($pid) {
   #parent: issue web page to confirm that assignment has been received
      <:\smvdots{}:>
 } else {
   #child: launch a possibly lengthy autograde program
      $gradeResults =  _autograde.pl -stop cleanup $grader $login_;
      $logFile = $assignmentDirectory . "grading.log";
      open (GRADELOG, ">>$logFile")
   }} die ("Could not append to $logFile: $!\n");
      print GRADELOG "$gradeResults\n";
      close GRADELOG;
 }

Here is part of the Perl script that gets launched when you submit programming assignments from the course’s web-based submissions pages:

This is run as a CGI script when you click the submit button on the assignment submission pages. Now, the actual auto-grading may take several minutes, but web browsers will time out long before then. So the script forks to create a new process. The new child runs the auto-grader. In the meantime, the writes out a quick “Thank you for submitting” page that the browser can show immediately.


Process Communication

Unix processes can communicate via a pipe, a communication path that appears to the program as a pair of files, one for input, the other for output.

Reading from an empty pipe will block a process until data is available (written into the pipe by another process).


Example of a Pipe

 char str[6];
 int mypipe[2];
 int status;
 pipe (mypipe); /* establishes a pipe.         */
                /* We read from mypipe[0] and  */
                /* write to mypipe[1]          */
 
 if (fork() == 0) {     /* child */
    read(mypipe[0], str, 6);
    printf (str);
 } else { /* parent */
    write (mypipe[1], "Hello", 6);
    wait (&status);
 }
 close mypipe[0];
 close mypipe[1];

This is “C” style I/O. Files are identified by an integer file handle, so out array of two ints is actually a pair of file handles. Each process can read from mypipe[0] and write to mypipe[1], though in this particular example one process does all the writing and the other all the reading.


Overall

This approach to process communication is


Library vs. Language

Library-based concurrency tends to be non-portable.

2.2 Heavy & Light Weight Processes

A Unix process includes its entire memory image


Threads

A thread is a light-weight process that

Some OS’s support this directly

3. Java Threads


Example: ThreadArt


Using the counter to affect the drawing


public void drawLines(Graphics g, Dimension d)
{
    int dmin = (d.width < d.height) ? d.width : d.height;

    if (stepSize < 1)
        stepSize = 1;

    Point center = new Point();
    center.x = (double)d.width/2.0;
    center.y = (double)d.height/2.0;

    int k = Math.abs(cycleCounter - cycleLength/2);
    int theta = 60 * cycleCounter / cycleLength; 

    for (int i = 0; i < 60; ++i) {
        int radius = dmin/2;
        Point origin = ptOnCircle(6*i+thetai, radius, center);
        int j = i + stepSize;
        while (j >= 60)
            j -= 60;
        while (i != j) {
            Point destination = ptOnCircle(6*j+theta, radius, center);
            Color c = iinterpolate(colors[0], colors[1], k, cycleLength/2);i
            g.setColor(c);
            g.drawLine ((int)origin.x, (int)origin.y,
                    (int)destination.x, (int)destination.y);
            j += stepSize;
            while (j >= 60)
                j -= 60;
        }
    }
}


3.1 Taking the Cheap Way Out

The easiest way to animate this code is to

ThreadArtByTimer.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;


/**
 * A simple example of GUI event handling in a Java application.
 * 
 * This can be run as a main program or as an applet.
 * 
 * @author zeil
 *
 */


public class ThreadArtByTimer extends JApplet {

    private boolean startedInAnApplet;

    // The Model
    private Color[] colors;
    private int stepSize = 5;

    private int cycleLength;
    private int cycleCounter;

    // The View & Controls
    private JFrame window;
    private JPanel canvas;
    private JButton colorChooser1;
    private JButton colorChooser2;
    private JTextField stepSizeIn;
    
    private Timer timer;


    private class ColorChooser implements ActionListener {
        private JButton button;
        private int colorNum;

        public ColorChooser (JButton button, int colorNum) {
            this.button = button;
            this.colorNum = colorNum;
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            Color chosen = JColorChooser.showDialog(window, "Choose a color", colors[colorNum]);
            if (chosen != null) {
                colors[colorNum] = chosen;
                setColor (button, chosen);
                canvas.repaint();
            }
        }
    };


    /**
     * Action that slowly changes the color of the drawing 
     *
     */
    public class ColorChanger implements ActionListener {

        public ColorChanger()
        {
        }
        
        

        @Override
        public void actionPerformed(ActionEvent arg0) {
            cycleCounter = (cycleCounter + 1) % cycleLength;
            canvas.repaint();
        }
        
    }




    public ThreadArtByTimer()
    {
        startedInAnApplet = false;
        window = null;
        colors = new Color[2];
        colors[0] = Color.red;
        colors[1] = Color.blue;
        cycleLength = 100;
        cycleCounter = 0;
    }


    public static void main (String[] args)
    {
        ThreadArtByTimer instance = new ThreadArtByTimer();
        instance.createAndShowGUI();  
    }

    public void createAndShowGUI() {
        window = new JFrame();
        // set up the components
        window.getContentPane().setLayout (new BorderLayout());

        canvas = new JPanel () {
            public void paint (Graphics g) {
                super.paint(g);
                drawLines (g, getSize());
            }
        };
        canvas.setBackground(Color.white);
        window.getContentPane().add (canvas, BorderLayout.CENTER);
        canvas.setPreferredSize(new Dimension(400, 400));

        JPanel controls = new JPanel();

        colorChooser1 = new JButton("Color 1");
        controls.add (colorChooser1);
        setColor(colorChooser1, colors[0]);
        colorChooser1.addActionListener (new ColorChooser(colorChooser1, 0));

        colorChooser2 = new JButton("Color 2");
        controls.add (colorChooser2);
        setColor(colorChooser2, colors[1]);
        colorChooser2.addActionListener (new ColorChooser(colorChooser2, 1));

        stepSizeIn = new JTextField (""+stepSize, 5);
        controls.add (stepSizeIn);
        stepSizeIn.addActionListener (new ActionListener()
        {
            public void actionPerformed(ActionEvent e) {
                try {
                    Integer newSize = new Integer(stepSizeIn.getText());
                    stepSize = newSize.intValue();
                    canvas.repaint();
                } catch (Exception ex) {};
            }
        });

        window.getContentPane().add (controls, BorderLayout.SOUTH);

        window.setDefaultCloseOperation((startedInAnApplet) ? JFrame.DISPOSE_ON_CLOSE : JFrame.EXIT_ON_CLOSE);


        timer = new Timer(50, new ColorChanger());
        timer.start();

        window.pack();
        window.setVisible(true);
    }

    /**
     * Sets the background color of a button to the indicated color.
     * Changes the foreground to wither black or white, depending on
     * which will give more contrast agasint the new background.
     * 
     * @param button
     * @param color
     */
    private void setColor(JButton button, Color color) {
        button.setBackground(color);
        int brightness = color.getRed() + color.getGreen() + color.getBlue(); // max of 3*255
        if (brightness > 3*255/2) {
            // This is a fairly bright color. Use black lettering
            button.setForeground (Color.black);
        } else {
            // This is a fairly dark color. Use white lettering
            button.setForeground (Color.white);
        }
    }

    // Applet functions

    public void init() {
        startedInAnApplet = true;
    }

    public void start() {
        if (window == null)
            createAndShowGUI();
    }

    public void stop() {
    }

    public void destroy() {
    }




    int interpolate (int x, int y, int i, int steps)
    {
        return (i * x + (steps-i)*y) / steps;
    }


    Color interpolate(Color c1, Color c2, int i, int steps)
    {
        return new Color (interpolate(c1.getRed(), c2.getRed(), i, steps),
                interpolate(c1.getGreen(), c2.getGreen(), i, steps),
                interpolate(c1.getBlue(), c2.getBlue(), i, steps));
    }


    class Point {
        double x;
        double y;
    }

    Point ptOnCircle (int degrees, int radius, Point center) 
    {
        Point p = new Point();
        double theta = Math.toRadians((double)degrees);
        p.x = center.x + (double)radius * Math.cos(theta);
        p.y = center.y + (double)radius * Math.sin(theta);
        return p;
    }

    public void drawLines(Graphics g, Dimension d)
    {
        int dmin = (d.width < d.height) ? d.width : d.height;

        if (stepSize < 1)
            stepSize = 1;

        Point center = new Point();
        center.x = (double)d.width/2.0;
        center.y = (double)d.height/2.0;

        int k = Math.abs(cycleCounter - cycleLength/2);
        int theta = 60 * cycleCounter / cycleLength; 
        
        for (int i = 0; i < 60; ++i) {
            int radius = dmin/2; //interpolate(dmin/4, dmin/2, k, cycleLength/2);
            Point origin = ptOnCircle(6*i+theta, radius, center);
            int j = i + stepSize;
            while (j >= 60)
                j -= 60;
            while (i != j) {
                Point destination = ptOnCircle(6*j+theta, radius, center);
                Color c = interpolate(colors[0], colors[1], k, cycleLength/2);
                g.setColor(c);
                g.drawLine ((int)origin.x, (int)origin.y,
                        (int)destination.x, (int)destination.y);
                j += stepSize;
                while (j >= 60)
                    j -= 60;
            }
        }
    }


}


Let’s Pretend That Never Happened

3.2 Working with Threads


The Java Thread class


ThreadArt

ThreadArt.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;


/**
 * A simple example of GUI event handling in a Java application.
 * 
 * This can be run as a main program or as an applet.
 * 
 * @author zeil
 *
 */


public class ThreadArt extends JApplet {

    private boolean startedInAnApplet;

    // The Model
    private Color[] colors;
    private int stepSize = 5;

    private int cycleLength;
    private int cycleCounter;
    private boolean running;

    // The View & Controls
    private JFrame window;
    private JPanel canvas;
    private JButton colorChooser1;
    private JButton colorChooser2;
    private JTextField stepSizeIn;
    
    private Animator colorChanger;


    private class ColorChooser implements ActionListener {
        private JButton button;
        private int colorNum;

        public ColorChooser (JButton button, int colorNum) {
            this.button = button;
            this.colorNum = colorNum;
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            Color chosen = JColorChooser.showDialog(window, "Choose a color", colors[colorNum]);
            if (chosen != null) {
                colors[colorNum] = chosen;
                setColor (button, chosen);
                canvas.repaint();
            }
        }
    };


    /**
     * Thread that slowly changes the color of the drawing 
     *
     */
    public class Animator extends Thread {  ➊

        public Animator()
        {
        }
        
        
        public void run()                  ➋
        {
            running = true;
            while (running) {              ➌
                try {
                    sleep(50);             ➍
                } catch (InterruptedException e) {
                  break;
                }
                cycleCounter = (cycleCounter + 1) % cycleLength; ➎
                canvas.repaint();
            }
        }
        
    }




    public ThreadArt()
    {
        startedInAnApplet = false;
        window = null;
        colors = new Color[2];
        colors[0] = Color.red;
        colors[1] = Color.blue;
        cycleLength = 100;
        cycleCounter = 0;
        running = false;
    }


    public static void main (String[] args)
    {
        ThreadArt instance = new ThreadArt();
        instance.createAndShowGUI();  
    }

    public void createAndShowGUI() {
        window = new JFrame();
        // set up the components
        window.getContentPane().setLayout (new BorderLayout());

        canvas = new JPanel () {
            public void paint (Graphics g) {
                super.paint(g);
                drawLines (g, getSize());
            }
        };
        canvas.setBackground(Color.white);
        window.getContentPane().add (canvas, BorderLayout.CENTER);
        canvas.setPreferredSize(new Dimension(400, 400));

        JPanel controls = new JPanel();

        colorChooser1 = new JButton("Color 1");
        controls.add (colorChooser1);
        setColor(colorChooser1, colors[0]);
        colorChooser1.addActionListener (new ColorChooser(colorChooser1, 0));

        colorChooser2 = new JButton("Color 2");
        controls.add (colorChooser2);
        setColor(colorChooser2, colors[1]);
        colorChooser2.addActionListener (new ColorChooser(colorChooser2, 1));

        stepSizeIn = new JTextField (""+stepSize, 5);
        controls.add (stepSizeIn);
        stepSizeIn.addActionListener (new ActionListener()
        {
            public void actionPerformed(ActionEvent e) {
                try {
                    Integer newSize = new Integer(stepSizeIn.getText());
                    stepSize = newSize.intValue();
                    canvas.repaint();
                } catch (Exception ex) {};
            }
        });

        window.getContentPane().add (controls, BorderLayout.SOUTH);

        window.setDefaultCloseOperation((startedInAnApplet) ? JFrame.DISPOSE_ON_CLOSE : JFrame.EXIT_ON_CLOSE);


        colorChanger = new Animator();   ➏
        colorChanger.start();

        window.pack();
        window.setVisible(true);
    }

    /**
     * Sets the background color of a button to the indicated color.
     * Changes the foreground to wither black or white, depending on
     * which will give more contrast agasint the new background.
     * 
     * @param button
     * @param color
     */
    private void setColor(JButton button, Color color) {
        button.setBackground(color);
        int brightness = color.getRed() + color.getGreen() + color.getBlue(); // max of 3*255
        if (brightness > 3*255/2) {
            // This is a fairly bright color. Use black lettering
            button.setForeground (Color.black);
        } else {
            // This is a fairly dark color. Use white lettering
            button.setForeground (Color.white);
        }
    }

    // Applet functions

    public void init() {
        startedInAnApplet = true;
    }

    public void start() {
        if (window == null)
            createAndShowGUI();
    }

    public void stop() {
    }

    public void destroy() {
    }




    int interpolate (int x, int y, int i, int steps)
    {
        return (i * x + (steps-i)*y) / steps;
    }


    Color interpolate(Color c1, Color c2, int i, int steps)
    {
        return new Color (interpolate(c1.getRed(), c2.getRed(), i, steps),
                interpolate(c1.getGreen(), c2.getGreen(), i, steps),
                interpolate(c1.getBlue(), c2.getBlue(), i, steps));
    }


    class Point {
        double x;
        double y;
    }

    Point ptOnCircle (int degrees, int radius, Point center) 
    {
        Point p = new Point();
        double theta = Math.toRadians((double)degrees);
        p.x = center.x + (double)radius * Math.cos(theta);
        p.y = center.y + (double)radius * Math.sin(theta);
        return p;
    }

    public void drawLines(Graphics g, Dimension d)
    {
        int dmin = (d.width < d.height) ? d.width : d.height;

        if (stepSize < 1)
            stepSize = 1;

        Point center = new Point();
        center.x = (double)d.width/2.0;
        center.y = (double)d.height/2.0;

        int k = Math.abs(cycleCounter - cycleLength/2);
        int theta = 60 * cycleCounter / cycleLength; 
        
        for (int i = 0; i < 60; ++i) {
            int radius = dmin/2;
            Point origin = ptOnCircle(6*i+theta, radius, center);
            int j = i + stepSize;
            while (j >= 60)
                j -= 60;
            while (i != j) {
                Point destination = ptOnCircle(6*j+theta, radius, center);
                Color c = interpolate(colors[0], colors[1], k, cycleLength/2);
                g.setColor(c);
                g.drawLine ((int)origin.x, (int)origin.y,
                        (int)destination.x, (int)destination.y);
                j += stepSize;
                while (j >= 60)
                    j -= 60;
            }
        }
    }


}

4. Interleaving & Synchronization


Interleavings

Given 2 processes whose code looks like:

process 1 process 2
a; x;
b; y;
c; z;

Possible Orderings

First statement must be either a or x.


Possible Orderings (cont.)

Expanding that:

First statement must be either a or x.


and so on

Possible Orderings

abcxyz, abxcyz, abxycz, abxyzc, axbcyz, axbycz, axbyzc, axybcz, axybzc, axyzbc, xabcyz, xabycz, xabyzc, xaybcz, xaybzc, xayzbc, xyabcz, xyabzc, xyazbc, xyzabc

Although there are many possibilities, b never precedes a, c never precedes a or b, etc.


Interleavings

An interleaving of two sequences s and t is any sequence u


Describing Process Execution

4.1 Safety and Parallel Programs

Shared Resoruces

Processes often compete for shared resources.

In these cases, some interleaves are dangerous.


Safety

We say that a nondeterministic program is safe if all of the answers it provides for any input are acceptable.


Example

Processes with Shared Variable
process 1 process 2
x = x + 1; x = x + 1;

Assuming that x starts with a value of –1 before the two processes are started, what will the value of x be after both have finished?


We could get: 1

Processes with Shared Variable
process 1 process 2
x = x + 1; x = x + 1;

We could get: 0

Processes with Shared Variable
process 1 process 2
x = x + 1; x = x + 1;

Nondeterminism


We could get: 65536

Processes with Shared Variable
process 1 process 2
x = x + 1; x = x + 1;

65536 : Really?


Simultaneous Access to Compound Data

More realistically, any kind of compound data structure is likely to be sensitive to simultaneous update by different processes.

More specifically,


Example: Simultaneous Access to a Queue

Suppose that we have a queue of customer records,

Here we show the data and possible implementations of the functions to add to the end and remove from the front.

Is there a potential problem with simultaneous access to these structures? The sequence of pictures below show that, for a linked list implementation of the queue, we could indeed get into trouble if a queue has a single element in it and one thread then tries to add a new element while another thread tries to remove one. (There may be other scenarios in which simultaneous access would also fail.)


Queues Interrupted

Now suppose that one process tries to simultaneously add a record to the end, and another one tries to remove a record from the front.
Assume that the remove() call gets the CPU first.
And then the add() gets to run for a little while …
and then control switches back to the remove() call, which runs to the end.
And finally the add() is allowed to complete.

Our queue is now badly mangled.

4.2 Synchronization

Synchronization is a restriction of the possible interleavings of a set of processes.


Synch & Shared Resources

Synchronization often takes the form of protecting a shared resource:

Processes with Shared Variable
process 1 process 2
seize(x); seize(x);
x = x + 1; x = x + 1;
release(x); release(x);

Synchronization Narrows the Options

Processes with Shared Variable
process 1 process 2
seize(x); seize(x);
x = x + 1; x = x + 1;
release(x); release(x);

A Bullet, but not a Silver One

5. Liveness Properties

5.1 Deadlock

In deadlock, all processes are waiting on some shared resources, with none of them able to proceed.


Example: The Dining Philosophers


Simulating the Philosphers

Represent each philosopher as an independent process:

 loop
    pick up left fork;
    pick up right fork;
    eat;
    release forks;
    think
 end loop;

Demo

Try running Sun’s Demo of the dining philosophers.

You may need to play a bit with the timing control at the bottom, but after a while you will almost certainly see the system get into deadlock.


Avoiding Deadlock

This is a “classic” fix from the world of operating systems, where this problem often arose in conjunction with different programs requesting access to I/O devices. If one program grabbed control of the printer and then requested a card reader, while another program had already gotten control of the card reader and was asking for the printer, deadlock would result. The classic solution was to assign each I/O device a unique number and require programs to request the devices in ascending order.


In general…

This works very nicely in this specialized case, but there is no general technique for avoiding deadlock in arbitrary situations.

5.2 Livelock

A system is in livelock if at least one process is not waiting, but the system makes no progress.

 loop
    pick up left fork;
    seize right fork
       if available;
    if seized then
      eat;
      release forks;
    else
      release left fork;
    end if;
    think;
  end loop;

This can get into livelock as each philosopher gets locked into a cycle of:

 pick up left fork;
 release left fork;
 pick up left fork;
 release left fork;
 pick up left fork;
 release left fork;
    ⋮

5.3 Fairness

When multiple processes are waiting for a shared resource, and that resource becomes available, a scheduler must decide which of the waiting processes gets the resource.


What’s Fair?

Precise definition of fairness is difficult. Some possibilities:


Selfish Threads

Threads can contribute to unfairness by being “selfish”:

 class MyThread extends Thread {
   ⋮
   public void run()
   {
    while (true)
      ++counter;
   }
 }

Unless the run-time system preempts this thread, it will hog the CPU.


Yielding

 class MyThread extends Thread {
   ⋮
   public void run()
   {
    while (true) {
      ++counter;
      Thread.yield();
    }
   }
 }

Java allows threads to signal that they don’t mind losing the CPU by yielding to other threads.


Thread Priority

Java also allows programs to set relative priorities for different threads:

Thread cpuHog = new Thread() {
    ⋮
};
cpuHog.setPriority(Thread.MIN_PRIORITY);
cpuHog.start();

This could be useful if the GUI was sluggish because of the CPU cycles being burned up by this thread.

6. Safety

Concurrent programs are often non-deterministic.

For example, if we have some critical sections that are writing data to the same output file, and other critical sections that are updating a shared display on the screen, our would be safe if processes were mutually excluded from the file output critical sections and mutually excluded from the screen update critical sections, but we might be able to tolerate one process writing to the file while another one updated the screen.

6.1 Mutual Exclusion Mechanisms

Semaphores

A semaphore is an ADT with two atomic operations, seize and release, and a hidden integer value.


seize()


release()


General Semaphores


Yep, those were semaphores

process 1 process 2
seize(x); seize(x);
x = x + 1; x = x + 1;
release(x); release(x);

Dining Philosophers with Semaphores

 Semaphore fork[N];
 Philosopher(i):
 loop
   int first = min(i, i+1%N);
   int second = max(i, i+1%N);
   fork[first].seize();
   fork[second].seize();
   eat;
   fork[first].release();
   fork[second].release();
   think;
 end loop;


Recap

Semaphores are relatively low-level approach to synchronization

Monitors


Monitors

A monitor is an ADT in which only one process at a time can execute any of its member functions.


A Common Pattern


The Consumer-Producer Pattern

What could go wrong?


Synchronizing the Queue

We have seen earlier that queues are likely unsafe for simultaneous access.

We avoid these simultaneous update problems by making the queue synchronized.


Monitored Queue

 class MonitoredQueue {
    private <: ... :>
    public synchronized
       void enter(Object) { ... }
    public synchronized
      Object front() { ... }
    public synchronized
      void leave() { ... }
    public synchronized
      boolean empty() { ... }
    public
       int maxSize() { ... }
  }

No two threads are allowed to be simultaneously in synchronized member functions of the same object.

This is across the whole interface. e.g., One thread cannot be inside the enter function while another is in the front() function of that queue.

If we have multiple synchronized queue objects however, one thread could be adding to queue A while another thread is adding to queue B.


Java Monitors

Monitors are the preferred synchronization technique in Java, where they are created by marking functions as “synchronized”.

      MonitoredQueue q1 = new MonitoredQueue();
      MonitoredQueue q2 = new MonitoredQueue();

Monitored Statement Blocks

The synchronized member function declaration is a special case of synchronized statement blocks

syncblocks.java
 class Consumer extends Thread {
    private Queue q;
 
   public void run() {
      while (true) {
          synchronized (q) {
            Customer c = q.front();
            q.leave();
          };
          ... process customer ...
        }
      }
   }
 }

 class Producer extends Thread {
    private Queue q;
 
   public void run() {
      while (true) {
        Customer c = fetchData();
        synchronized (line1) {
           q.enter(c);
        }
     }
 }


Synchronization on Objects

7. Direct Control of Thread State

Waiting…

We often want to make a thread inactive until certain conditions are met.


Busy Wait

This is the bad way to do it:

public void run() {
   while (true) {
      if (conditionIsMet()) {
         doSomethingUseful();
      }
   }
}

This is a busy wait


Son of Busy Wait

Only marginally better:

public void run() {
   while (true) {
      if (conditionIsMet()) {
         doSomethingUseful();
      } else {
        sleep(100); // wait 0.1 seconds
      }
   }
}


Controlling the Process State

A better idea is to

wait()

If we have a synchronized lock on some object x, then x.wait() will


notifyAll()

If we have a synchronized lock on some object x, then x.notifyAll() will


Example: Adding a Pause Button to ThreadArt

ThreadArt2.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

/**
 * A simple example of GUI event handling in a Java application.
 * 
 * This can be run as a main program or as an applet.
 * 
 * @author zeil
 *
 */


public class ThreadArt2 extends JApplet {

    private boolean startedInAnApplet;

    // The Model
    private Color[] colors;
    private int stepSize = 5;

    private int cycleLength;
    private int cycleCounter;
    private boolean running;
    
    // The View & Controls
    private JFrame window;
    private JPanel canvas;
    private JButton colorChooser1;
    private JButton colorChooser2;
    private JButton pause;
    private JTextField stepSizeIn;
    
    private Animator colorChanger;


    private class ColorChooser implements ActionListener {
        private JButton button;
        private int colorNum;

        public ColorChooser (JButton button, int colorNum) {
            this.button = button;
            this.colorNum = colorNum;
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            Color chosen = JColorChooser.showDialog(window, "Choose a color", colors[colorNum]);
            if (chosen != null) {
                colors[colorNum] = chosen;
                setColor (button, chosen);
                canvas.repaint();
            }
        }
    };


    /**
     * Thread that slowly changes the color of the drawing 
     *
     */
    public class Animator extends Thread {

        public Animator()
        {
        }
        
        
        public void run()
        {
            running = true;
            while (true) {
                try {
                    sleep(50);
                } catch (InterruptedException e) {
                  break;
                }
                synchronized (this) {
                    while (!running) {
                        try {
                            wait();                           ➌
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                }
                cycleCounter = (cycleCounter + 1) % cycleLength;
                canvas.repaint();
            }
        }
        
    }




    public ThreadArt2()
    {
        startedInAnApplet = false;
        window = null;
        colors = new Color[2];
        colors[0] = Color.red;
        colors[1] = Color.blue;
        cycleLength = 100;
        cycleCounter = 0;
        running = true;
    }


    public static void main (String[] args)
    {
        ThreadArt2 instance = new ThreadArt2();
        instance.createAndShowGUI();  
    }

    public void createAndShowGUI() {
        window = new JFrame();
        // set up the components
        window.getContentPane().setLayout (new BorderLayout());

        canvas = new JPanel () {
            public void paint (Graphics g) {
                super.paint(g);
                drawLines (g, getSize());
            }
        };
        canvas.setBackground(Color.white);
        window.getContentPane().add (canvas, BorderLayout.CENTER);
        canvas.setPreferredSize(new Dimension(400, 400));

        JPanel controls = new JPanel();

        colorChooser1 = new JButton("Color 1");
        controls.add (colorChooser1);
        setColor(colorChooser1, colors[0]);
        colorChooser1.addActionListener (new ColorChooser(colorChooser1, 0));

        colorChooser2 = new JButton("Color 2");
        controls.add (colorChooser2);
        setColor(colorChooser2, colors[1]);
        colorChooser2.addActionListener (new ColorChooser(colorChooser2, 1));

        stepSizeIn = new JTextField (""+stepSize, 5);
        controls.add (stepSizeIn);
        stepSizeIn.addActionListener (new ActionListener()
        {
            public void actionPerformed(ActionEvent e) {
                try {
                    Integer newSize = new Integer(stepSizeIn.getText());
                    stepSize = newSize.intValue();
                    canvas.repaint();
                } catch (Exception ex) {};
            }
        });

        pause = new JButton("Pause");             ➊
        controls.add (pause);
        pause.addActionListener(new ActionListener() {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                if (running) {
                    running = false;              ➋
                    pause.setText("Resume");
                    pause.repaint();
                } else {
                    synchronized (colorChanger) {
                        running = true;           ➍
                        pause.setText("Pause");
                        pause.repaint();
                        colorChanger.notifyAll();
                    }
                }
            }
        });
        
        
        window.getContentPane().add (controls, BorderLayout.SOUTH);

        window.setDefaultCloseOperation((startedInAnApplet) ? JFrame.DISPOSE_ON_CLOSE : JFrame.EXIT_ON_CLOSE);


        colorChanger = new Animator();
        colorChanger.start();

        window.pack();
        window.setVisible(true);
    }

    /**
     * Sets the background color of a button to the indicated color.
     * Changes the foreground to wither black or white, depending on
     * which will give more contrast agasint the new background.
     * 
     * @param button
     * @param color
     */
    private void setColor(JButton button, Color color) {
        button.setBackground(color);
        int brightness = color.getRed() + color.getGreen() + color.getBlue(); // max of 3*255
        if (brightness > 3*255/2) {
            // This is a fairly bright color. Use black lettering
            button.setForeground (Color.black);
        } else {
            // This is a fairly dark color. Use white lettering
            button.setForeground (Color.white);
        }
    }

    // Applet functions

    public void init() {
        startedInAnApplet = true;
    }

    public void start() {
        if (window == null)
            createAndShowGUI();
    }

    public void stop() {
    }

    public void destroy() {
    }




    int interpolate (int x, int y, int i, int steps)
    {
        return (i * x + (steps-i)*y) / steps;
    }


    Color interpolate(Color c1, Color c2, int i, int steps)
    {
        return new Color (interpolate(c1.getRed(), c2.getRed(), i, steps),
                interpolate(c1.getGreen(), c2.getGreen(), i, steps),
                interpolate(c1.getBlue(), c2.getBlue(), i, steps));
    }


    class Point {
        double x;
        double y;
    }

    Point ptOnCircle (int degrees, int radius, Point center) 
    {
        Point p = new Point();
        double theta = Math.toRadians((double)degrees);
        p.x = center.x + (double)radius * Math.cos(theta);
        p.y = center.y + (double)radius * Math.sin(theta);
        return p;
    }

    public void drawLines(Graphics g, Dimension d)
    {
        int dmin = (d.width < d.height) ? d.width : d.height;

        if (stepSize < 1)
            stepSize = 1;

        Point center = new Point();
        center.x = (double)d.width/2.0;
        center.y = (double)d.height/2.0;

        int k = Math.abs(cycleCounter - cycleLength/2);
        int theta = 60 * cycleCounter / cycleLength; 
        
        for (int i = 0; i < 60; ++i) {
            int radius = dmin/2; //interpolate(dmin/4, dmin/2, k, cycleLength/2);
            Point origin = ptOnCircle(6*i+theta, radius, center);
            int j = i + stepSize;
            while (j >= 60)
                j -= 60;
            while (i != j) {
                Point destination = ptOnCircle(6*j+theta, radius, center);
                Color c = interpolate(colors[0], colors[1], k, cycleLength/2);
                g.setColor(c);
                g.drawLine ((int)origin.x, (int)origin.y,
                        (int)destination.x, (int)destination.y);
                j += stepSize;
                while (j >= 60)
                    j -= 60;
            }
        }
    }


}

Note the placement of wait() calls inside loops. That’s a safety measure. If more than one thread is waiting to get something from the queue, and only one element is added to the queue, then all the waiting threads will be awakened by notifyAll(), but only one will actually get the data and the rest will immediately have to wait() again.

8. Summing Up

Making Things Parallel