Threads

Most users are familiar with multitasking - editing a spreadsheet at the same time the computer is printing a word document. Unless you are using a machine with multi-processors what is happening is the operating system is rapidly cycling between the different activities, allowing each to execute for a small amount of time.

Multithreading is the same concept applied to an individual program. Each program has the ability to execute a number of tasks at the same time. Each of these computational tasks is called a thread. Multiple threads exist in the same executing environment. This means they share the same data variables, access the same methods, and so on. As with multitasking, this parallel behavior is usually just a charade; in fact the operating system is still executing only one thread at any one time and is cycling among all the different threads to give each an equal share.

A thread is a path of execution within a program that is executed separately. One way to visualize a thread is to imagine that you could make a list of the program's statements as the CPU executes them. Thus for a particular execution of a program with loops, method calls, and selection statements, you could list each instruction that was executed, beginning at the first and continuing until the program stopped, as a single sequence of executed statements. That is a thread! At runtime, threads in a program have a common memory space and can therefore share data and code, i.e. they are lightweight.

An application can be made user-friendly by allowing the user to interact with the application while it is running. One way to do this is to place the execution of the program in a separate thread, leaving the main program thread free to listen for user activity. In Java, threads are created using the class Thread. There are two common ways that this class is used. One way is to subclass from Thread, and override the method run(), which is a method that is executed to perform the task assigned to a thread. The start() method inherited from the Thread class is invoked to make the thread eligible for running.

public class ThreadObj extends Thread {
	public void run () {
	}
}

Platforms differ on how they assign time to threads. On some systems threads are run until they sleep or until they yield to another thread. On other systems threads are given a fixed amount of time to execute, then halted automatically if they have not yet given up control when their time unit is finished. In order to provide maximum portability, threads should either sleep occasionally or periodically invoke the method yield() that is inherited from class Thread. Either of these actions will halt the current thread, allowing other threads a chance to perform their actions.

If a class already inherits from another class it cannot inherit from Thread since Java does not allow multiple inheritance. There is an alternative technique - the class implements the interface Runnable. When a new thread class is defined by extending Thread, it inherits a start() method from Thread and overrides the run() method defined in the parent class. A thread belonging to this class is started by calling start() to it, which in turn will call run. However, a class implementing Runnable has a run method but no start method. Consequently to start and run such a class requires two steps. First an instance of the Runnable object is created. Then an object of type Thread is created using a constructor that takes the Runnable object as argument.


public class RunObject implements Runnable {
...
}
Thread threadObj = new Thread (new RunObject());
threadObj.start();

When creating threads, implementing the Runnable interface is usually preferred to extending the Thread class.

Thread Safety

Threads share the same memory space, i.e. they can share resources. Two or more threads frequently need to share the same data fields. Threads are asynchronous. This means that the order of execution and the timing of a set of threads are non-deterministic. It is impossible to predict when and for how long an individual thread will run. You cannot even assume that a thread will be able to complete a Java arithmetic operation once it has started it.

If two threads attempt to modify the same data area, it is possible for erroneous values to be produced. This is known as a race hazard. A class is thread safe if it ensures the consistency of the states of the objects and the results of method invocation in the presence of multiple threads. A monitor (semaphore) is used to synchronize access to a shared resource or critical region. Threads gain access to a shared resource or critical region by first acquiring the monitor associated with the resource or region. At any given time, no more than one thread can own the monitor and thereby have access to the shared resource or region. A monitor thus implements a mutually exclusive locking mechanism (mutex). There are two ways in which code can be synchronized:

Synchronized methods are useful in situations where methods can manipulate the state of an object in ways that can corrupt the state if executed concurrently, e.g. push and pop operations on a stack.

public synchronized char pop() {...}
public synchronized void push (char ch) {...}

While a thread is inside a synchronized method of object, all other threads that wish to execute this synchronized method or any other synchronized method of the object will have to wait. The non-synchronized methods of the object can of course be called at any time by any thread.

The synchronized block allows arbitrary code to be synchronized on the monitor of an arbitrary object.


Synchronized (object reference) {code block}

The braces of the block cannot be left out, even if the code block has just one statement. Once a thread has entered the code block after acquiring the monitor on the specified object, no other thread will be able to execute the code block until the monitor is released. In contrast to synchronized methods, this mechanism allows fine-grained synchronization of code on arbitrary objects.

class SmartClient {
	BankAccount account;

	public void updateTransaction() {
		synchronized (account) {
			account.update();
		}
	}
}

Inner object might need to synchronize on its associated outer object, in order to ensure integrity of data in the latter.


class Outer_A 
{
  private double x;
  protected class Inner_B 
  {
    public void setX() 
    {
      synchronized(Outer_A.this) {x = 2;}
    }
  }
}

Life Cycle of a Thread

Each Thread has a life cycle that consists of several different states. Just because a thread's start() method has been called does not mean that the thread has access to the CPU. A thread can be in on of the following states:

Thread Priorities

The thread scheduler decides which thread gets to run. Threads are assigned priorities that the thread scheduler can use to determine how the threads will be treated. The thread scheduler usually decides to let the thread with the highest priority in the Ready-to-run state get CPU time. This is not necessarily the thread that has been the longest time in the ready-to-run state. Priorities are integer values from 1 (lowest priority) to 10 (highest priority). The range in priority values is from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY. A thread inherits the priority of its parent thread. Priority of a thread can be set by using the setPriority() method and read by using the getPriority() method. Schedulers employ one of the two following strategies: A call to method yield() will cause the current running thread to move to the Ready-to-run-state in order to give other threads a chance to run. A call to the method sleep() will cause the thread to move to the Sleeping state.

public static void sleep (long millisec) throws InterruptedException

Waiting and notifying provide a means of communication between threads that synchronize on the same object.

void wait (long timeout) throws InterruptedException
void notify()
void notifyAll()

The wait() method is designed so that a thread which is expecting some condition to occur can leave the Running state and transit to the Waiting state to wait for this condition to occur. When a thread executing the synchronized method pop() on a Stack object finds that the stack is empty, it executes a wait() method. The wait(), notify(), and notifyAll() methods must be executed in synchronized code, otherwise the call will result in an IllegalMonitorStateException.

Cooperation among Threads

In some applications it is necessary to synchronize and coordinate the behavior of threads to enable them to carry out a cooperative task. Many cooperative applications are based on the producer/consumer model. According to this model, two threads cooperate at producing and consuming a particular resource or piece of data. The producer thread creates some message or result, and the consumer thread reads or uses the result.

One application for this model would be to control the display of data that is read by your browser. As information arrives from the Internet, it is written to a buffer by the producer thread. A separate consumer thread reads information from the buffer and displays it in your browser window. When the producer attempt to put new data in the buffer when it is full should wait for the consumer to display some it. When the consumer wants to display data from an empty buffer it should wait for the producer to put some data there.

Communication between two threads of control is accomplished via a single integer variable (currentCommand). This value represents the next command to be executed. This variable is both set and accessed by means of the method getCommand(). Note that this method is declared as synchronized. This means that only one of the two threads can be executing the method at any one time. If the other process attempts to execute the method, it will be held up until the first process completes.

Liveness Failure

Liveness refers to the condition that something positive will happen eventually. For example, the task will be completed in finite amount of time or that the thread should always respond to user input.

Contention: Contention is also called starvation or indefinite postponement. This happens when a runnable thread does not get a chance to run. The situation arises when there are always one or more threads with higher priorities. Or there is a thread with the same priority but it never yields. To avoid contention threads with high priority should periodically sleep() or yield() to allow other threads to run.

Dormancy: This happens when a thread is blocked and never becomes runnable. A thread could be blocked by wait() and never awakened by notify() or notifyAll(). To avoid dormancy make sure that each thread blocked by wait() is again awakened by another thread that invokes the notify() or notifyAll() methods.

Deadlock: When two or more threads block each other none can make progress. Two or more threads competing for multiple shared resources simultaneously cause deadlock. Detecting and preventing deadlock is difficult.

Premature Termination: When a thread is terminated before it should be, it impedes the progress of other threads.