Architecture & DesignInterthread Communication in Java Multithreaded Programming

Interthread Communication in Java Multithreaded Programming

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

In a multitasking platform, interthread communication through Java can leverage subtle control over threads than by using implicit monitors. Java provides an out-of-the-box solution to many multi-threading problems. Here in this article, we shall delve into what Java has to offer for interthread communication in a multithreaded programming environment.

An Overview

Java provides extensive built-in support for programming in a thread-based, multitasking environment. A thread, basically, is a small unit of dispatchable code. Multiple such units can run simultaneously, in effect leveraging CPU cycle time. This feat can be achieved with a multitasking process instead of a thread, but they are heavier and have more overhead, such as each of them requires separate address spaces. Threads, on the other hand, share resources (code, data, heap—except the stack) among its peers, which makes it lighter for context switching as well as interthread communication (in contrast to inter-process communication). The goal of multitasking is to write efficient programs that optimally use the processing power of the underlying system. The thread seems to have an upper hand at it, that’s all.

Process-based multitasking is not in the direct control of Java because it is mostly system (operating environment, such as the Operating System) based. But, for multitasking with threads, Java provides an excellent support.

Refer to the articles “Understanding the Java Thread Model” and “A Deeper Look: Java Thread Example” for a glimpse on the basics of Java threads.

Multithreading in Java

A thread divides tasks into discrete logical units and Java provides the necessary APIs to manipulate it according to the programmer’s needs. Java multithreading is built upon the Thread class and its methods. For convenience, there is an associated interface, called Runnable, which can be implemented if the Thread class is not extended to create a thread. When multiple threads are spawned, it is incumbent on the programmer to ensure that the main thread or the program that spawns all other thread finishes last. This allays some of the nasty thread core dump bugs incorporated into the program. Java provides the isAlive() method to brief on the status of the thread. For example, a true value returned by this method means that the thread is still running, we then can write appropriate code to interrupt or stop the thread. There is another convenient method called join(), which is used to make a thread wait until all spawned threads finish execution.

Here is a simple program to illustrate the use of the join() method.

package org.mano.example;
public class Main implements Runnable {
   private String threadName;
   private Thread t;
   public Main(String tname) {
      threadName = tname;
      t = new Thread(this, threadName);
      System.out.println("New Thread: " + t);
   }
   public static void main(String[] args) {
      Main[] m = new Main[10];
      for (inti = 0; i < m.length; i++) {
         m[i] = new Main( "Thread " + (i + 1));
         m[i].t.start();
      }
      try {
         for (inti = 0; i < m.length; i++)
         m[i].t.join();
      } catch (InterruptedException ex) {
         System.out.println("Main thread interrupted");
      }
      System.out.println("Main thread exiting");
   }
   @Override
   public void run() {
      try {
         for (int i = 1; i < 10; i++) {
            System.out.println(threadName+ " : " + t);
            Thread.sleep(1000);
         }
      } catch (InterruptedException ex) {
         System.out.println(threadName+ " interrupted");
      }
      System.out.println(threadName + " exiting.");
   }
}

A thread can be prioritised by using the setPriority() method. However, note that execution of a thread depends upon several factors besides priority. The availability of CPU time is actually at the consideration of the underlying multitasking platform. Prioritising indicates the thread scheduler should give more importance to it over other low priority running threads, sometime by pre-empting the lower priority threads. Note that threads of equal priority should get an equal chance of execution, but the real situation may not be according to the theory. The crux of the matter is that once a thread is created, its execution pattern is non-deterministic. But, we can manipulate some parts of it.

When two or more threads run a resource, sharing is an issue. They must be synchronized to avoid problems such as a race condition. Java uses the implicit monitor to resolve the issue and offers a keyword called synchronize, which can be used as a indicator to designate a critical method or a critical block of code.

Interthread Communication

The use of implicit monitors usually blocks asynchronous access to certain resources. Interthread communication enables a more subtler control it. Although event loop programming is a gross alternative to multithreading, the benefit of using a thread to realize multitasking far outweighs event loops. The event loop leads to polling. Polling waits for a certain event to occur by repeatedly checking for a certain condition. This means that the consumer of the event or the event handler has to be recurrent just about the production of event, without doing any actual processing. This is clearly a waste of CPU time. And, once the event is generated, polling starts, which further wastes CPU time.

Java overcame this problem via interthread communication. The interthread communication is done with the help of three final methods defined in the Object class: wait(), notify(), and notifyAll().

    • The wait() method puts the current thread into a sleep state and releases the monitor until some other thread holds the monitor and invokes the notify() or notifyAll() method. There are overloaded variation of the wait() method.
      • public final void wait() throws
           InterruptedException
      • public final void wait(long timeout) throws
           InterruptedException
      • public final void wait(long timeout, int nanos)
           throws InterruptedException

These methods enable the thread to wait until the notify/notifyAll method is invoked or a specified amount of time has passed as a parameter has elapsed. The parameters give a finer control over amount of time to wait.

  • The notify() method wakes up the thread which is waiting on this object’s monitor.
  • The notifyAll() method wakes up all the thread waiting on this object’s monitor while granting access to one of them.

These methods are invoked within a designated synchronized block. Here is a quick example of the rudimentary implementation of the classic producer consumer and problem which illustrates how interthread communication may be done.

A Quick Example

package org.mano.example;
public class Buffer {
   private int num = -1;
   private Boolean state = false;
   public synchronized void set(int n) {
      try {
         while (state) {
            wait();
         }
      } catch (InterruptedException ix) {
         ix.printStackTrace();
      }
      num = n;
      state = true;
      notify();
   }
   public synchronized int get() {
      try {
         while (!state) {
            wait();
         }
      } catch (InterruptedException ix) {
         ix.printStackTrace();
      }
      state = false;
      System.out.println("Item " + num);
      notify();
      return num;
   }
}

package org.mano.example;
import java.util.Random;
public class Producer implements Runnable {
   private final Buffer syncBuffer;
   Thread t;
   private final Random r = new Random();
   public Producer(Buffer b) {
      syncBuffer = b;
      t = new Thread(this, "Producer");
   }
   @Override
   public void run() {
      int i = 0;
      while (i++ < 10) {
         int val = r.nextInt(100);
         syncBuffer.set(val);
         System.out.println("Produced value: " + val);
      }
   }
}

package org.mano.example;
import java.util.Random;
public class Consumer implements Runnable {
   private final Buffer syncBuffer;
   Thread t;
   public Consumer(Buffer b) {
      syncBuffer = b;
      t = newThread(this, "Consumer");
   }
   @Override
   public void run() {
      int i = 0;
      while (i++ < 10) System.out.println
         ("Consumed value: " + syncBuffer.get());
   }
}

package org.mano.example;
public class Main {
   public static void main(String[] args) {
      Buffer buff = new Buffer();
      Producer p = new Producer(buff);
      Consumer c = new Consumer(buff);
      p.t.start();
      c.t.start();
   }
}

Conclusion

The whole effort of multitasking lies in dealing with how the threads enter and exit a critical section in a synchronized manner without deadlocks and possible starvation. Deadlock occurs when two or more threads have a circular dependency on a synchronized object. And, starvation is when one or more thread is not getting a CPU cycle time among competing threads. Java provides ample API support to deal with multitasking through threads. Interthread communication is just a part of dealing with a specific aspect of multithreaded programming.

Reference

Java API Documentation

Related Topics

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories