C SC 225 Lecture 13: Multi-threading and Concurrent Programming

[ previous | schedule | next ]

Basic Definitions

Threads versus Processes

Creating and using threads in Java

Two techniques for creating a thread class
  1. Extend the Thread class
  2. Implement the Runnable interface
Extend the Thread class
class MyThread extends Thread {
   . . .
   public void run() {
      //  thread executes this code
   }
}

class Client {
   . . .
   public Whatever anyMethod() {
      . . .
      Thread doIt = new MyThread();
      doIt.start();   
      // continue on without waiting for thread to complete
   }
}

Implement the Runnable interface
class MyThread implements Runnable {
   . . .
   public void run() {
      //  thread executes this code
   }
}

class Client {
   . . .
   public Whatever anyMethod() {
      . . .
      Thread doIt = new Thread(new MyThread());
      doIt.start(); 
      // continue on without waiting for thread to complete
   }
}

Both will do the same thing. From a design standpoint, which is preferable and why?

Special things a thread can do

static Thread currentThread() method void start() method void run() method static void sleep() method void join() method static void yield() method

Thread States

Thread Scheduling

Thread Blocking

The current thread can block itself by:

The current thread may be blocked by JVM when: The current thread may be removed from execution by JVM but remain runnable when

Thread Interruption

Thread Synchronization

Bad things can happen when two different threads share the same memory!

Classic example is Producer-Consumer a.k.a. Bounded Buffer problem

Another Example: two threads call acct.withdraw() at nearly the same time:

1    public boolean withdraw(long amount) {
2       if (amount <= balance) {
3          long newBalance = balance – amount;
4          balance = newBalance;
5          return true;
6       } else {
7          return false;
8       }
9    }

Consider this execution sequence involving 2 threads A and B:

  1. balance is 1500
  2. Thread A calls acct.withdraw(1000)
  3. Thread A does line 2, condition is true
  4. Thread A does line 3, computes newBalance as 500
  5. JVM Scheduler yanks thread A and replaces it with thread B
  6. Thread B calls acct.withdraw(1000) on the same account (acct)
  7. Thread B does line 2, condition is true (balance is still 1500)
  8. Thread B does line 3, newBalance as 500
  9. Thread B does line 4, assigns balance 500
  10. Thread B does line 5, returns true
  11. JVM Scheduler yanks thread B and replaces it with thread A
  12. Thread A does line 4, assigns balance 500!
  13. Thread A does line 5, returns true!
Not such a good deal for the bank!

Critical Sections and Concurrent Programming in Java

Java has two major mechanisms for implementing critical sections and concurrent programming
  1. A monitor associated with every Java object
  2. The java.util.Concurrent package, its classes and subpackages. Added with Java 1.5 (5.0)

Java Monitors (a.k.a. locks)

Returning to the previous example: In Java, you can declare the method to be synchronized:
1    public synchronized boolean withdraw(long amount) {
2       if (amount <= balance) {
3          long newBalance = balance – amount;
4          balance = newBalance;
5          return true;
6       } else {
7          return false;
8       }
9    }

Now, when Thread A calls acct.withdraw(1000), B cannot enter it until A has returned. Thread A gets the lock on object acct. When Thread A is finished, the lock is released and B gets it. Thread A will return true, B will return false.

Can also synchronize individual statements or blocks of statements using synchronize block

public Whatever anyMethod() {
   // non-critical stuff
   synchronized(this) {
      // critical operations
   }
   // non-critical stuff
}
Primitive variables declared volatile, can be thread-safe in limited situations without using synchronized.

The java.util.Concurrent Package

Deadlock (a.k.a. "The Deadly Embrace")

Just because you carefully synchronize methods, that doesn't mean bad things cannot happen in a multi-threaded environment! Consider this sequence: Neither thread can continue, locked in a "deadly embrace"! Each is waiting for a resource that the other has an exclusive lock on.

A related condition is livelock, in which the threads are technically still alive because they are written to periodically wake up and re-test a condition that will never become true:

         while (!condition) {
             sleep(awhile);
         }
Deadlocks are so insidious because the conditions that lead to them are timing-related, possibly based on external events, and therefore may be very difficult to reproduce.

The same is true of any thread synchronization problem.


[ C SC 225 | Peter Sanderson | Math Sciences server  | Math Sciences home page | Otterbein ]

Last updated:
Peter Sanderson (PSanderson@otterbein.edu)