Software Engineering
Concurrency

Course Map

Agenda

Multi-Threading in Java

Subclassing Thread

GreetingThread Outline

A program to print a time stamp and "Hello World" once a second for ten seconds
public class GreetingThread extends Thread
{
   public void run()
   {
      //thread action
      . . .
   }
   //variables used by the thread action
   . . .
}

Thread Action for GreetingThread

GreetingThread

GreetingThread run method

public run()
{
   try
   {
      //thread action
   )
   catch (InterruptedException exception)
   {
      //cleanup, if necessary
   }
}

File GreetingThread.java

To Start the Thread

File GreetingThreadTest.java

Output

Thu Dec 28 23:12:03 PST 2004 Hello, World!
Thu Dec 28 23:12:03 PST 2004 Goodbye, World!
Thu Dec 28 23:12:04 PST 2004 Hello, World!
Thu Dec 28 23:12:05 PST 2004 Hello, World!
Thu Dec 28 23:12:04 PST 2004 Goodbye, World!
Thu Dec 28 23:12:05 PST 2004 Goodbye, World!
Thu Dec 28 23:12:06 PST 2004 Hello, World!
Thu Dec 28 23:12:06 PST 2004 Goodbye, World!
Thu Dec 28 23:12:07 PST 2004 Hello, World!
Thu Dec 28 23:12:07 PST 2004 Goodbye, World!
Thu Dec 28 23:12:08 PST 2004 Hello, World!
Thu Dec 28 23:12:08 PST 2004 Goodbye, World!
Thu Dec 28 23:12:09 PST 2004 Hello, World!
Thu Dec 28 23:12:09 PST 2004 Goodbye, World!
Thu Dec 28 23:12:10 PST 2004 Hello, World!
Thu Dec 28 23:12:10 PST 2004 Goodbye, World!
Thu Dec 28 23:12:11 PST 2004 Goodbye, World!
Thu Dec 28 23:12:11 PST 2004 Hello, World!
Thu Dec 28 23:12:12 PST 2004 Goodbye, World!
Thu Dec 28 23:12:12 PST 2004 Hello, World!

Implementing Runnable

Often more flexible to implement Runnable than extend Thread

  1. Create an object of your subclass that implements Runnable
    Runnable r = new MyRunnable();
  2. Construct a Thread object from the runnable object.
    Thread t = new Thread(r);
  3. Call the start method to start the thread.
    t.start();

 

 1 public class Animal {
 2 }

 1 public class Cat extends Animal implements Runnable {
 2
 3     private final String msg;
 4     private final long sleepTime;
 5
 6     public Cat(String msg, long sleepTime) {
 7         this.msg = msg;
 8         this.sleepTime = sleepTime;
 9     }
10
11     public void run() {
12
13         while (true) {
14
15             System.out.println(msg);
16             try {
17                 Thread.sleep(sleepTime);
18             }
19             catch (InterruptedException e) {
20             }
21         }
22     }
23 }

 1 public class CatTest {
 2
 3     // Args to this application specify "msg"
 4     // and "sleepTime" for multiple threads.
 5     // For example, the command:
 6     //
 7     // $ java CatTest Meow 100 Grrr 1000
 8     //
 9     // requests two threads, one that prints
10     // out "Meow" every 100 milliseconds and
11     // another that prints out "Grrr" every
12     // 1000 milliseconds.
13     //
14     public static void main(String[] args) {
15
16         // Require an even argCount
17         int argCount = args.length;
18         if ((argCount / 2) == 1) {
19             --argCount;
20         }
21
22         for (int i = 0; i < argCount; i += 2) {
23
24             String msg = args[i];
25             long sleepTime = Long.parseLong(args[i + 1]);
26
27             Cat cat = new Cat(msg, sleepTime);
28
29             Thread catThread = new Thread(cat);
30             catThread.start();
31         }
32     }
33 }

Thread Scheduler

Self Check

  1. What happens if you change the call to the sleep method in the run method to Thread.sleep(1)?
  2. What would be the result of the program if the main method called
    t1.run();
    t2.run();
    instead of starting threads?

Answers

  1. The messages are printed about one millisecond apart.
  2. The first call to run would print ten "Hello" messages, and then the second call to run would print ten "Goodbye" messages.

Terminating Threads

Terminating Threads

public void run() {
   try
   {
      for (int = 1;
           i <= REPETITIONS && !isInterrupted();
           i++)
      {
         //do the work
      }
   }
   catch (InterruptedException exception)
   {
	// exit interrupted thread
   }
   //cleanup
}

Making a Thread Sleep and Controlled Stopping

Applying Thread.sleep() or inJava 5 the utility TimeUnit class

Clock.java

volatile
Roughly speaking, a volatile field is safe for concurrent use by two or more threads. More accurately, volatile says that the value of a field must always be read from to main memory and that it may not be cached by a thread (in register or CPU cache).


Self Check

  1. Suppose a web browser uses multiple threads to load the images on a web page. Why should these threads be terminated when the user hits the "Back" button?

Self Check

  1. Consider the following runnable.
    public class MyRunnable implements Runnable
    {
       public void run()
       {
          try
          {
             System.out.println(1);
             Thread.sleep(1000);
             System.out.println(2);
          }
          catch (InterruptedException exception)
          {
             System.out.println(3);
          }
          System.out.println(4);
       }
    }
    Suppose a thread with this runnable is started and immediately interrupted.
    Thread t = new Thread(new MyRunnable());
    t.start();
    t.interrupt();
    What output is produced?

Answers

  1. If the user hits the "Back" button, the current web page is no longer displayed, and it makes no sense to expend network resources for fetching additional image data.
  2. The run method prints the values 1, 3, and 4. The call to interrupt merely sets the interruption flag, but the sleep method immediately throws an InterruptedException.

Race Conditions

Sample Application

Scenario to Explain Non-zero Result

Race Condition

Corrupting the Contents of the balance Field

Corrupting the Contents of the balance Field

File BankAccountThreadTest.java

File DepositThread.java

File WithdrawThread.java

File BankAccount.java

Output

Depositing 100.0, new balance is 100.0
Withdrawing 100.0, new balance is 0.0
Depositing 100.0, new balance is 100.0
Withdrawing 100.0, new balance is 0.0
. . .
Withdrawing 100.0, new balance is 400.0
Depositing 100.0, new balance is 500.0
Withdrawing 100.0, new balance is 400.0
Withdrawing 100.0, new balance is 300.0

Self Check

  1. Give a scenario in which a race condition causes the bank balance to be -100 after one iteration of a deposit thread and a withdraw thread.
  2. Suppose two threads simultaneously insert objects into a linked list. Explain how the list can be damaged in the process.

Answers

  1. There are many possible scenarios. Here is one:
  2. One thread calls addFirst and is preempted just before executing the assignment first = newLink. Then the next thread calls addFirst, using the old value of first. Then the first thread completes the process, setting first to its new link. As a result, the links are not in sequence.

Synchronizing Object Access

Synchronizing Object Access

Synchronizing Object Access

Synchronizing Object Access

Synchronizing Object Access

Self Check

  1. If you construct two BankAccount objects, how many lock objects are created?
  2. What happens if we omit the call unlock at the end of the deposit method?

Answers

  1. Two, one for each bank account object. Each lock protects a separate balance field.
  2. When a thread calls deposit, it continues to own the lock, and any other thread trying to deposit or withdraw money in the same bank account is blocked forever.

Avoiding Deadlocks

Avoiding Deadlocks

Condition Objects

Thread Cooperation: Condition Objects

Thread Cooperation: Condition Objects

File BankAccountThreadTester.java

File BankAccount.java

File DepositRunnable.java

File WithdrawRunnable.java

Output

Depositing 100.0, new balance is 100.0
Withdrawing 100.0, new balance is 0.0
Depositing 100.0, new balance is 100.0
Depositing 100.0, new balance is 200.0
. . .
Withdrawing 100.0, new balance is 100.0
Depositing 100.0, new balance is 200.0
Withdrawing 100.0, new balance is 100.0
Withdrawing 100.0, new balance is 0.0

Self Check

  1. What is the essential difference between calling sleep and await?
  2. Why is the sufficientFundsCondition object a field of the BankAccount class and not a local variable of the withdraw and deposit methods?

Answers

  1. A sleeping thread is reactivated when the sleep delay has passed. A waiting thread is only reactivated if another thread has called signalAll or signal.
  2. The calls to await and signal/signalAll must be made to the same object.

Solving the Race Condition Problem - Low-Level Approach

Synchronized Methods: Mutual Exclusion

public class BankAccount
{
   public synchronized void deposit(double amount)
   {
      . . .
   }

   public synchronized void withdraw(double amount)
   {
      . . .
   }
   . . .
}

Monitors and Non-reentrant  Mutually Exclusive Locks (Mutex)

A monitor is a body of code (not necessarily contiguous, developed by C. A. R. Hoare), access to which is guarded by a mutual-exclusion locks (or mutex) that is associated with an object. The central notion of mutex is ownership. Only one thread can own the mutex at a time. If a second thread tries to acquire ownership, it will block (be suspended) until the owning thread releases the mutex.

synchronized(obj) {

	// guard code

}

is effectively the same as this code:

obj.myMutex.acquire();
try {

	// guard code

}
finally {

	obj.myMutex.release();

}

Synchronized Method:  Mutual Exclusion

Synchronized Methods: Mutual Exclusion

Visualization of Synchronized Thread Behavior

Deadlock

Thread Cooperation with Mutexes

withdraw Method to Cooperate and Avoid Deadlock

public synchronized void withdraw(double amount)
   throws InterruptedException
{
   while (balance < amount)
      wait();
}

Object.wait() and Object.notify(), Object.notifyAll()

 

Restroom wait/notifyAll Analogy

The Java Monitor

Thread Blocking

The Thread-Safe Object

class IsSafe {

	double x;

	public void synchronized mark() (
		x = 0;
	}

	public void john() {
		x = -1;
	}
}

 

File BankAccountThreadTest.java

Using synchronized methods: cooperation

File BankAccount.java

Counting Semaphores (1965, E. W. Dijkstra)

Conceptually, a semaphore maintains a set of permits. Each acquire() blocks if necessary until a permit is available, and then takes it. Each release() adds a permit, potentially releasing a blocking acquirer. However, no actual permit objects are used; the Semaphore just keeps a count of the number available and acts accordingly.

Controlling Access to a Pool of Items

The semaphore encapsulates the synchronization needed to restrict access to the pool, separately from any synchronization needed to maintain the consistency of the pool itself.

class Pool {
   private static final MAX_AVAILABLE = 100;
   private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

   public Object getItem() throws InterruptedException {
     available.acquire();
     return getNextAvailableItem();
   }

   public void putItem(Object x) {
     if (markAsUnused(x))
       available.release();
   }

   // Not a particularly efficient data structure; just for demo
   protected Object[] items = ... whatever kinds of items being managed
   protected boolean[] used = new boolean[MAX_AVAILABLE];

   protected synchronized Object getNextAvailableItem() {
     for (int i = 0; i < MAX_AVAILABLE; ++i) {
       if (!used[i]) {
          used[i] = true;
          return items[i];
       }
     }
     return null; // not reached
   }

   protected synchronized boolean markAsUnused(Object item) {
     for (int i = 0; i < MAX_AVAILABLE; ++i) {
       if (item == items[i]) {
          if (used[i]) {
            used[i] = false;
            return true;
          } else
            return false;
       }
     }
     return false;
   }

 }
 

Other Synchronizers

 

Animation

Algorithm Animation

Selection Sort Algorithm Animation

Selection Sort Algorithm Animation

Selection Sort Algorithm Animation

Selection Sort Algorithm Animation

Selection Sort Algorithm Animation

Applet to Provide User Interface

Applet to Provide User Interface

Applet to Provide User Interface

Applet to Provide User Interface

A Step in the Animation of the Selection Sort Algorithm

Step in the Animation of the Selection Sort Algorithm

File SelectionSortApplet.java

File SelectionSorter

ArrayUtil

Making a System Call

public Process exec(String command)
             throws IOException

public Process exec(String command,
                    String[] envp)
             throws IOException

public Process exec(String command,
                    String[] envp,
                    File dir)
             throws IOException

public Process exec(String[] cmdarray)
             throws IOExceptionjava

public Process exec(String[] cmdarray,
                    String[] envp)
             throws IOException

public Process exec(String[] cmdarray,
                    String[] envp,
                    File dir)
             throws IOException

 

An example using Runtime: DoRuntime

   import java.io.*;
   import java.util.*;
   
   public class DoRuntime {
     public static void main(String args[]) throws IOException {
       if (args.length <= 0) {
         System.err.println("Need command to run");
         System.exit(-1);
       }
       Runtime runtime = Runtime.getRuntime();
       Process process = runtime.exec(args);
       InputStream is = process.getInputStream();
       InputStreamReader isr = new InputStreamReader(is);
       BufferedReader br = new BufferedReader(isr);
       String line;
       System.out.printf("Output of running %s is:", 
           Arrays.toString(args));
       while ((line = br.readLine()) != null) {
         System.out.println(line);
       }
       // check for a command failure  
       try {
           if (process.waitFor() != 0) 
                   System.err.println("exit value = " +
                   process.exitValue());
            }
       }
       catch (InterruptedException e) {
           System.err.println(e);
       }
     }
    } 
  java DoRuntime ls
  Output of running ls is:DoRuntime.class
  DoRuntime.java   
  >java DoRuntime "cmd /c dir"
   Volume in drive D is Users
   Volume Serial Number is 2841-2ED0
   Directory of D:\...

  04/15/2006  09:30 AM    <DIR>          .
  04/15/2006  09:30 AM    <DIR>          ..
  04/15/2006  09:30 AM             1,146 DoRuntime.class
  04/15/2006  09:23 AM               724 DoRuntime.java
  ...

System Call in a Different Directory

    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(command);
to:
    File file = new File(other directory);
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(command, null, file);

 

An example using ProcessBuilder: DoProcessBuilder

   import java.io.*;
   import java.util.*;
   
   public class DoProcessBuilder {
     public static void main(String args[]) throws IOException {
       if (args.length <= 0) {
         System.err.println("Need command to run");
         System.exit(-1);
       }
       Process process = new ProcessBuilder(args).start();
       InputStream is = process.getInputStream();
       InputStreamReader isr = new InputStreamReader(is);
       BufferedReader br = new BufferedReader(isr);
       String line;
       System.out.printf("Output of running %s is:", 
          Arrays.toString(args));
       while ((line = br.readLine()) != null) {
         System.out.println(line);
       }
      
       // check for a command failure  
       try {
           if (process.waitFor() != 0) 
                   System.err.println("exit value = " +
                   process.exitValue());
            }
       }
       catch (InterruptedException e) {
           System.err.println(e);
       }
     }
    } 
 
  
  > java DoProcessBuilder ls
  Output of running ls is:DoProcessBuilder.class 
  DoProcessBuilder.java
  DoRuntime.class
  DoRuntime.java   
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(command);
were changed to the following line in DoProcessBuilder:
    Process process = new ProcessBuilder(command).start();

 

Using ProcessBuilder

public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)
public ProcessBuilder directory(File directory)
   ProcessBuilder processBuilder = new ProcessBuilder(command);
   Map<String, String> env = processBuilder.environment();
   // manipulate env
   ProcessBuilder processBuilder = new ProcessBuilder(
                                       command, arg1, arg2);
   Map<String, String> env = processBuilder.environment();
   env.put("var1", "value");
   env.remove("var3");
   processBuilder.directory("Dir");
   Process p = processBuilder.start();