CS 4773 Object Oriented Systems
Threads and Synchronization

Start of class on Wednesday


Previous Topic: Threads

About Threads
Terminology
Creating Threads
Thread States
Thread Priorities and Scheduling
Importance of Understanding Scheduling
Synchronization in Java
A Bounded Buffer Example
CRC Cards

Next Topic: Vectors and Synchronization


Note: Much of this material comes from Concurrent Programming, The Java Programming Language by Stephen Hartley, Oxford Press, 1998. and The Java Language Specification by Gosling, Joy, and Steele, Addison Wesley, 1996.


About Threads


Terminology

The Java language specification has a complete list of how these actions are allowed to interact with each other.

You can think about this as having each thread run on a processor with local memory (working memory) and a distributed main memory.

The assignment a = b; might result in the following operations:

The read and write operations may require moving the data through an interconnection network and may take an unpredictable length of time.

Actions performed by a single thread are totally ordered.

However, actions performed by different threads may not be.

The main result is that shared variables should be manipulated only within locked code.
A lock is not released until all pending writes have been completed.


Creating Threads (a review)

There are two ways to create a thread:

Thread States


Thread Priorities and Scheduling

Each thread has a priority which can affect its scheduling.

The scheduling algorithm is not specified by Java and is left up to the implementation.


Importance of Understanding Scheduling

Consider a system which uses preemptive scheduling with the following four threads: Suppose MD has the shared buffer locked and IB preempts it.
IB blocks when it attempts to access the shared buffer and MD can get back in to finish and unlock the buffer.
This is OK.

Suppose that MD has the shared buffer locked and IB preempts it and is blocked.
Before MD can get in, CT gets the CPU and prevents MD from running, thus blocking IB.
The effect is that a medium priority process, CT has blocked a high priority process, MD.
This is called priority inversion.
Now WD gets the CPU and sees that no I/O has occurred for a long time (which is an error condition because IB should be doing I/O frequently).
WD thinks an error has occurred and resets the system.

Remember the Pathfinder Mars mission? This is exactly what was causing the system resets on the Pathfinder.

MD was a meteorological data gathering task.
IB was the information bus task.
CT was a communication task.
WD was the watchdog timer

The problem was fixed by changing the system (from earth) to use what is called priority inheritance.
When a higher priority process blocks because a lower priority process holds a lock, the process holding the lock inherits the priority of the other process.

This may hold the record for longest distance remote programming.

You can read more about this here.


Synchronization in Java

Synchronization is done in Java using monitors.

Each instance of a class has its own monitor with one (anonymous) condition variable.

Recall that a monitor is a compiler construct which allows only one process to be active (own the monitor) within the protected methods of the monitor.

In Java, by default, no methods of the object are protected. You can protect methods by using the synchronized key word.

How to own a monitor of an object

        synchronized (obj) {
           code goes here
        }
Here are some of the important methods related to thread synchronization:
Examples:

This thread sleeps for about 100 milliseconds:

   try {
      sleep(100)
   }
   catch (InterruptedException e) {}
If this is executed in a method of an object that does not extend Thread, then it might have to be written:
   try {
      Thread.sleep(100);
   }
   catch (InterruptedException e) {}
Wait for the thread T to die:
   try { 
      T.join();
   }
   catch (InterruptedException e) {} 
Wait and notify:
   public synchronized void test1() {
      while (count == 0)
         try {
            wait();
         catch (InterruptedException e) {}
      ...
   }

   public synchronized void test2() {
      ...
      count++;
      if (count == 1)
         notify();
   }
Note that the two methods must be in the same object (same monitor) and executed by different threads.

A Bounded Buffer Example

In the bounded buffer problem a single finite buffer is controlled by an arbitrary number of producer and consumer threads.

The bounded buffer might be implemented as a circular queue using and array, buffer with integer variables putIm, takeOut, and count:

putIn: index of the entry to next receive an item (next empty entry)
takeOut: index of the entry that will be tken out next (oldest entry)
count: number of filled entries in the buffer

The following code does not work even if there are no exceptions:

Producer code:

   public synchronized void deposit(double value) { 
      if (count == numSlots)
         try {
            wait(); 
         } catch (InterruptedException e) {}
      buffer[putIn] = value;
      putIn = (putIn + 1) % numSlots;
      count++;
      if (count == 1)
          notify();
   }

Consumer code:

   public synchronized double fetch() {
      double value;
      if (count == 0)
         try {
            wait();
         } catch (InterruptedException e) {}
      value = buffer[takeOut];
      takeOut = (takeOut + 1) % numSlots;
      count--;
      if (count == numSlots-1) 
         notify();
      return value;
   }
}
Why does it not work?

Suppose the buffer is empty and there are three threads in the ready queue:

C1 blocks and P notifies it.
C1 is put back in the ready queue.
C2 gets the CPU before C1.
C2 consumes the item.
C1 consumes an item which is not there!

One way to fix this problem is to put the wait in a while loop:

   public synchronized void deposit(double value) {  
      while (count == numSlots) 
         try {
            wait();  
         } catch (InterruptedException e) {} 
      buffer[putIn] = value; 
      putIn = (putIn + 1) % numSlots; 
      count++;
      if (count == 1)
          notify();
   } 
  
   public synchronized double fetch() { 
      double value; 
      while (count == 0)
         try { 
            wait(); 
         } catch (InterruptedException e) {} 
      value = buffer[takeOut]; 
      takeOut = (takeOut + 1) % numSlots;
      count--;
      if (count == numSlots-1) 
         notify();
      return value; 
   }  
} 
This still has a problem. Suppose the buffer is empty and the ready queue looks like this: After the two consumers get in and are blocked, P1 inserts and item and wakes up one of the consumers, say C1.
Suppose that C1 is put in the ready queue after P2.
P2 produces a second item and does not notify since the count is now 2.
C1 consumes its item but C2 never wakes up.

We can fix this problem by waking up all waiting processes.

 
   public synchronized void deposit(double value) {   
      while (count == numSlots)  
         try { 
            wait();   
         } catch (InterruptedException e) {}  
      buffer[putIn] = value;  
      putIn = (putIn + 1) % numSlots;  
      count++; 
      if (count == 1) 
          notifyAll(); 
   }  
  
   public synchronized double fetch() {  
      double value;  
      while (count == 0) 
         try {  
            wait();  
         } catch (InterruptedException e) {}  
      value = buffer[takeOut]; 
      takeOut = (takeOut + 1) % numSlots; 
      count--; 
      if (count == numSlots-1)  
         notifyAll(); 
      return value;  
   }   
}  

CRC Cards

CRC cards is a technique for object oriented design. The leters stand for Class, Responsibilities, Collaborations.

A CRC card is a 3 by 5 index card for a given class. The first line contains the name of the class.
Next is a list of responsibilities of the class, that is the public methods of that class. Finally, the collaborations are listed. These are the classes that this class needs to know about in order to implement its public methods.

Design steps:


Chutes and Ladders Example

CRC Cards Designed in Class, Wednesday, February 24, 2000


Next topic: Vectors and Synchronization