The best programs are written so that computing machines can perform them quickly and so that human beings can understand them clearly. A programmer is ideally an essayist who works with traditional aesthetic and literary forms as
well as mathematical concepts, to communicate the way that an algorithm works and to convince a reader that the results will be correct. Donald E. Knuth

Multithreading

A thread is defined as a flow of execution in a program. So, far we have seen only single threaded programs which perform only one task at a time. A multithreaded program is the one which consists of two or more parts
( threads ) which can run concurrently. Each thread has a separate flow of execution. One thread can do some mathematical operations and in parallel other thread can display some animation in the background. Java supports the concept of multithreading which is a unique feature and a powerful programming tool.

Process vs Threads
A program in execution ( running program ) is known as process. Each process has its own address space. A thread is considered to be a lightweight process since multiple threads share the same address space. A process may consists of one or more threads executing in parallel. The term lightweight signifies that a thread uses less resources as compared to a process. Multiple processes running simultaneously is known as multiprocessing and multiple threads executing together is known as multithreading. Both multithreading and multiprocessing are forms of multitasking.
How do we achieve multitasking in a single processor environment ?
At any given point of time, a CPU can be allocated to one task ( process / thread ) only. However, the CPU switches between the tasks so fast that it gives an impression that various tasks are happening in parallel. Switching of CPU from one task to another is known as context-switch and operating system is responsible for this switching.

Creating Multithreaded Programs in Java
Thread creation in Java can be done in two ways :
1 ) Extending the Thread class.
2 ) Implementing the Runnable interface.

Thread class provides inbuilt support for creating and running a Thread. We just need to override the run( ) method to define the functionality of the thread. In the following program, we will create two threads and execute them in parallel :

/* define a class 'A' which inherits from class 'Thread'
 * ( create 1st thread )
 */
class A extends Thread {
   public void run() { // override the run() method of 'Thread' class
      for ( int i = 1; i <= 5; i++ ) {
         System.out.println("Thread A ... i = " + i);
      }
      System.out.println("Exit from Thread A : Execution over");
   }
}

/* define a class 'B' which inherits from class 'Thread'
 * ( create 2nd thread )
 */
class B extends Thread {
   public void run() {
      for ( int i = 11; i <= 15; i++ ) {
         System.out.println("Thread B ... i = " + i);
      }
      System.out.println("Exit from Thread B : Execution over");
   }
}

public class ThreadDemo {
   public static void main(String args[]) {
      A thread_a = new A();
      B thread_b = new B();
      thread_a.start(); /* start the 1st thread ( start() is a method
                       defined in 'Thread' class ) */
      thread_b.start(); /* start the 2nd thread */

      /* we can replace the above set of statements by :-
         new A().start();
         new B().start();
      */
   }
}

The output of the above program cannot be predicted. It depends on how the context switch happens between the two threads. One possible output could be :

Thread B ... i = 11
Thread B ... i = 12
Thread A ... i = 1
Thread B ... i = 13
Thread A ... i = 2
Thread B ... i = 14
Thread A ... i = 3
Thread B ... i = 15
Exit from Thread B : Execution over
Thread A ... i = 4
Thread A ... i = 5
Exit from Thread A : Execution over

The above sequence of output may change every time you run the program.

Next, we will see how to create threads using Runnable interface. We need to implement the run( ) method of the interface and call thread's start( ) method to execute the thread. Following program demonstrates the use of Runnable interface :

/* define a class 'A' which implements 'Runnable' interface
 * ( create 1st thread )
 */
class A implements Runnable {
   public void run() { // define the run() method of the interface
      for ( int i = 1; i <= 5; i++ ) {
         System.out.println("Thread A ... i = " + i);
      }
      System.out.println("Exit from Thread A : Execution over");
   }
}

/* define a class 'B' which implements 'Runnable' interface
 * ( create 2nd thread )
 */
class B implements Runnable {
   public void run() {
      for ( int i = 6; i <= 10; i++ ) {
         System.out.println("Thread B ... i = " + i);
      }
      System.out.println("Exit from Thread B : Execution over");
   }
}

public class RunnableDemo {
   public static void main(String args[]) {
      A obj_a = new A();
      B obj_b = new B();
      Thread thread_a = new Thread(obj_a);
      Thread thread_b = new Thread(obj_b);
      thread_a.start(); // start the 1st thread
      thread_b.start(); // start the 2nd thread

      /* we can replace the above set of statements by :-
         new Thread(new A()).start();
         new Thread(new B()).start();
      */
   }
}


Methods of Thread class to stop or block a thread
A thread goes to dead state when it reaches the end of run( ) method. However, to cause premature death of a thread ( to stop the thread at any point ), we can use stop( ) method.
sleep( ) method blocks the thread for a specified time. The thread blocks when sleep( ) method is used and wakes up ( continues running ) when the specified time is elapsed.
suspend( ) method ( deprecated now since it is deadlock prone ) also blocks the thread and resume( ) method is invoked to unblock the thread.
wait( ) method blocks the thread until certain condition occurs and notify( ) method is invoked to unblock it. notifyAll( ) method notifies all the threads waiting for certain event to occur.
yield( ) method relinquishes control to some other thread.
Following program illustrates the use stop( ), sleep( ) and yield( ) methods :

class A extends Thread {
   public void run() {
      for ( int i = 1; i <= 5; i++ ) {
         System.out.println("Thread A ... i = " + i);
         if ( i == 3 ) {
            try {
               sleep(800); // sleep for 800 ms
            } catch(Exception e) { }
         }
      }
      System.out.println("Exit from Thread A : Execution over");
   }
}

class B extends Thread {
   public void run() {
      for ( int i = 11; i <= 15; i++ ) {
         System.out.println("Thread B ... i = " + i);
         if ( i == 12 ) {
            yield(); // relinquish control to some other thread
         }
      }
      System.out.println("Exit from Thread B : Execution over");
   }
}

class C extends Thread {
   public void run() {
      for ( int i = 21; i <= 25; i++ ) {
         System.out.println("Thread C ... i = " + i);
         if ( i == 23 ) {
            stop(); // thread execution stops
         }
      }
      System.out.println("Exit from Thread C : Execution over");
   }
}

public class StopBlockDemo {
   public static void main(String args[]) {
      new A().start();
      new B().start();
      new C().start();
   }
}

In the above program, we have used sleep( ) method inside try ... catch block to handle any error or exception. We will elaborate exception handling in the next section. We will see sample programs to demonstrate other thread control methods mentioned above later in this section.

Life cycle of a thread
A thread goes through various stages / states during its life cycle. These states are briefly described below :
Newborn : A thread is said to be in new born state when a Thread object is created but not yet started.
Running : The thread is started and CPU is allocated to it ( i.e the thread is currently in execution ).
Runnable : The thread is ready for execution and waiting for the availability of CPU.
Waiting : The thread is blocked and cannot enter the runnable state. sleep( ), wait( ) or suspend( ) methods puts                a thread in runnable state to blocked state. Similarly, resume( ) and notify( ) methods puts a blocked                thread to runnable state.
Dead : A thread moves to terminated or dead state when execution of run( ) method is over. We can forcefully kill            a thread by using stop( ) method.

Thread Priority
Operating System schedules the threads in Java according to the priority of threads. So, far we have seen threads of equal priority which are given equal treatment by the scheduler. Java allows us to set the priority of a thread using setPriority( ) method.
Thread class defines the following priority constants :
MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10
Higher priority threads are given more preference as compared to low priority threads by the scheduler. By default, every thread is given NORM_PRIORITY ( 5 ). Following program illustrates how to set the priority of threads :

class A extends Thread {
   public void run() {
      System.out.println("Thread A priority : " + this.getPriority());
      for ( int i = 1; i <= 5; i++ ) {
         System.out.println("Thread A ... i = " + i);
      }
      System.out.println("Exit from Thread A : Execution over");
   }
}

class B extends Thread {
   public void run() {
      System.out.println("Thread B priority : " + this.getPriority());
      for ( int i = 11; i <= 15; i++ ) {
         System.out.println("Thread B ... i = " + i);
      }
      System.out.println("Exit from Thread B : Execution over");
   }
}

public class PriorityDemo {
   public static void main(String args[]) {
      A thread_a = new A();
      B thread_b = new B();
      thread_a.setPriority(Thread.MIN_PRIORITY);
      thread_b.setPriority(Thread.MAX_PRIORITY);
      thread_a.start(); /* start the 1st thread */
      thread_b.start(); /* start the 2nd thread */
   }
}

Synchronization
Consider a situation in which multiple threads try to access a shared resource ( for e.g, trying to access a class variable or method or a common file ). This can lead to consistency problems. We must ensure that the shared resource is accessed and used by only one thread at a time. We can achieve this using a concept called thread synchronization. In Java, the keyword synchronized is used to create a critical section of code which can be accessed by only one thread at a time. Basically, a locking mechanism is introduced. A thread which holds the lock can access the critical section and once the job is done, it releases the lock. Let us understand the significance of synchronization using two versions of a program, one using synchronized keyword and the other without it.
Problem : Suppose have a class which provides a simple functionality of printing a range of numbers. We want to print different ranges with multiple threads executing in parallel. Following programs illustrate the scenarios with and without synchronization :

Program without synchronization ( output gets overlapped )
class Range {
   /* method is not synchronized.
    * context switch can occur at any point within the method body.
    * More than one thread can be inside the method at any point.
    */
   void display(int low, int high) {
      System.out.println("Display between " + low + " and " + high);
      for(int i = low; i <= high; i++) {
         System.out.print(i + " ");
      }
      System.out.println();
   }
}

/* Helper class to print the range */
class Helper extends Thread {
   Range r;
   int low, high;
   Helper(Range r_obj, int low_limit, int high_limit) {
      r = r_obj;
      low = low_limit; high = high_limit;
   }
   public void run() {
      r.display(low, high);
   }
}

public class SyncProblemDemo {
   public static void main(String args[]) {
      /* we create one object to print different ranges */
      Range obj = new Range();
      /* create a thread to print between 1 to 10 */
      Helper th1 = new Helper(obj, 1, 10);
      /* create another thread to print between 51 to 60 */
      Helper th2 = new Helper(obj, 51, 60);
      th1.start();
      th2.start();
   }
}

Output could be in any unpredictable order like the one shown below :
Display between 1 and 10
Display between 51 and 60
1 51 2 52 3 53 4 54 5 55 6 56 7 57 8 58 9 59 60 10

Program with synchronization
class Range {
   /* Method body of display() is a critical section and only one thread
    * can be inside synchronized method. Other thread can enter this method
    * only when first thread is done with its task.
    */
   synchronized void display(int low, int high) {
      System.out.println("Display between " + low + " and " + high);
      for(int i = low; i <= high; i++) {
         System.out.print(i + " ");
      }
      System.out.println();
   }
}

/* Helper class to print the range */
class Helper extends Thread {
   Range r;
   int low, high;
   Helper(Range r_obj, int low_limit, int high_limit) {
      r = r_obj;
      low = low_limit; high = high_limit;
   }
   public void run() {
      r.display(low, high);
   }
}

public class SynchronizationDemo {
   public static void main(String args[]) {
      /* we create one object to print different ranges */
      Range obj = new Range();
      /* create a thread to print between 1 to 10 */
      Helper th1 = new Helper(obj, 1, 10);
      /* create another thread to print between 51 to 60 */
      Helper th2 = new Helper(obj, 51, 60);
      th1.start();
      th2.start();
   }
}

Output is in expected order :
Display between 1 and 10
1 2 3 4 5 6 7 8 9 10
Display between 51 and 60
51 52 53 54 55 56 57 58 59 60

Example of wait( ) and notify( ) methods
Now that we seen how synchronization works in Java, let's explore wait( ) and notify( ) methods. We have previously seen sleep( ) method which blocks the current thread for a specified duration of time. The difference between sleep( ) and wait( ) methods is that sleep( ) is called on a thread while wait( ) is called on an object.
The thread which invokes the wait( ) method on an object remains blocked until another thread invokes the
notify( ) or notifyAll( ) method on the same object. The wait( ) method releases all the locks held by the current thread whereas sleep( ) method doesn't.
Following program illustrates the use of wait( ) and notify( ) methods :

/* This class computes sum and average of a given set of no.s */
class SumAvg {
   int set[];
   int sum = 0;
   boolean sum_computed = false;
   SumAvg(int arr[]) {
      set = arr;
   }
   void sum() {
      synchronized(this) { // acquire a lock on the current object
         try {
            System.out.println("Entered sum() method");
            for ( int i = 0; i < set.length; i++ ) {
               sum += set[i];
            }
            sum_computed = true;
            notify(); /* notify the waiting thread that sum has been computed */
         } catch(Exception e) { }
      }
   }
   void average() {
      synchronized(this) { // acquire a lock on the current object
         try {
            System.out.println("Entered average() method");
            while(!sum_computed) {
               System.out.println("Waiting for sum to be computed ");
               wait(); //call to wait() method releases the acquired locks
               System.out.println("Sum has been computed ");
            }
            float avg = (float)sum / set.length;
            System.out.println("Avg = " + avg);
         } catch(Exception e) { }
      }
   }
}

/* This is a helper class to compute the sum of all elements in the array */
class SumHelper extends Thread {
   SumAvg sa;
   SumHelper(SumAvg obj) {
      sa = obj;
   }
   public void run() {
      sa.sum();
   }
}

/* This is a helper class to compute the average of all numbers */
class AvgHelper extends Thread {
   SumAvg sa;
   AvgHelper(SumAvg obj) {
      sa = obj;
   }
   public void run() {
      sa.average();
   }
}

public class WaitNotifyDemo {
   public static void main(String args[]) {
      int arr[] = { 56, 43, 12, 9, 39 };
      SumAvg sa = new SumAvg(arr);
      /* create different threads to compute sum and average */
      SumHelper sh = new SumHelper(sa); // this thread computes sum
      AvgHelper ah = new AvgHelper(sa); // this thread computes average
      ah.start();
      sh.start();
   }
}

In the above program, we start two threads to compute the sum of array elements and compute the average. However to compute the average, sum must be available. So the thread computing the average blocks using the wait( ) method and the other thread operating on the same object unblocks the first thread using notify( ) method. Please note that instead of synchronizing the whole method, we have synchronized a block using 'this' which means any other thread that synchronizes on the same object will have to wait until the lock is released.

Deadlock
Consider the following scenario :
There are two threads A and B and two resources R1 and R2. A has acquired a lock on R1 and at the same time B has acquired a lock on R2. Now, A needs access to resource R2 to complete its task and release the held locks. To make the situation worse, B needs access to R1 before it can release the held locks. Thus, both the threads are waiting for each other to release the locks to proceed further. This situation is known as deadlock and it occurs due to improper use of synchronization.
Following program demonstrates deadlock situation in Java :

class Division {
   String str1 = "cse";  // resource 1
   String str2 = "geek"; // resource 2

   void div1() { // compute str1 + str2
      synchronized(str1) { // acquire a lock on str1
         System.out.println("Thread1 : Acquired lock on str1");
         try {
            Thread.sleep(1000);
         } catch(Exception e) { }
         synchronized(str2) { // acquire a lock on str2
            System.out.println("Thread1 : Acquired lock on str2");
            String str = str1 + str2;
            System.out.println(str1 + "+" + str2 + " = " + str);
         }
      }
   }

   void div2() { // computes str2 + str1
      synchronized(str2) { // acquire a lock on str2
         System.out.println("Thread2 : Acquired lock on str2");
         try {
            Thread.sleep(1000);
         } catch(Exception e) { }
         synchronized(str1) { // acquire a lock on str1
            System.out.println("Thread2 : Acquired lock on str1");
            String str = str2 + str1;
            System.out.println(str2 + "+" + str1 + " = " + str);
         }
      }
   }
}

public class DeadlockDemo {
   public static void main(String args[]) {
      final Division dv = new Division();

      /* instantiate a Thread object to call div1() method */
      Thread dh1 = new Thread() {
         public void run() {
            dv.div1();
         }
      };

      /* instantiate another Thread object to call div2() method */
      Thread dh2 = new Thread() {
         public void run() {
            dv.div2();
         }
      };

      dh1.start();
      dh2.start();
   }
}

In the above program, one thread acquires a lock on str1 and then goes into sleep so that CPU goes to other thread and that thread can acquire a lock on str2. The use of sleep( ) method increases the possibility of deadlock. Also note that we haven't used any helper class which extends Thread class, rather we have instantiated two objects of Thread class inside the main( ) method. This program will hang if executed because both the threads will wait forever.
How to avoid deadlocks ?
Deadlocks can be prevented by enforcing an order in which locks can be obtained by threads. For e.g, in the above program if both the threads first obtain lock on str1 and then str2, then the problem won't occur.

Final Comments
We have already seen and used some of the common methods of Thread class. To know about all the members, please refer to the documentation of the class.

Back | Next