Unlike many other computer languages, Java provides built in support for multithreaded programming. A multithreaded program contains two or more parts that can run concurrently. Each part of such a program is called a thread, and each thread defines a separate path of execution. Multithreading is specialized for of multitasking. It is important to understand the difference between the two. For most readers, process based multitasking is the more familiar form. A process is, in essence, a program that is executing. Thus, process based multitasking is the feature that allows you to run two or more programs concurrently. For example, we can run java compiler and same time we can also use some text editor. In process based multitasking, a program is the smallest unit of code that can be dispatched by the scheduler.

In a thread –based multitasking environment, the thread is the smallest unit of dispatchable code. This means that a single program can perform two or more tasks simultaneously. For example, a text editor can format text at the same time that is printing, as long as these two actions are being performed by two separate threads. Thus, process based multitasking deals with the “big picture” and thread based multitasking handles the detail.

Process: A process can be defined a program in execution. It has a self contained environment. A process generally has a complete, private set of basic run time resources; in particular, each process has its own memory space.

Threads:  A program can be composed of multiple independent flow of control. Java supports the creation of programs with concurrent flows of control. These independent flows of control are called threads.

Threads run with in a program and make use of that program’s resources in their execution. For this reason threads are also called lightweight processes (LWP).

Both processes and threads provide an execution environment, but creating a new thread requires fewer resources than creating a new process.

Main Thread: Every application has at least one thread or several. But from the application programmer’s point of view, we start with one thread, called the main thread. This thread has the ability to create additional threads.

The Java Thread Model: The Java run time System depends on thread for many things, and all the class libraries are designed with multithreading in mind. In fact, Java uses threads to enable the entire environment to be asynchronous. This helps reduce inefficiency by preventing the waste of CPU cycles.

The value of a multithreaded environment is best understood in contrast to its counterpart. Single threaded systems use an approach called an event loop with polling. In this model, a single thread of control runs in an infinite loop, polling a single event queue to decide what to do next. Once this polling mechanism returns with, say, a signal that a network file is ready to be read, then the event loop dispatches control to the appropriate event handler. Until this event handler returns, nothing else can happen in the system. This wastes CPU time. It can also result in one part of a program dominating the system and preventing any other events from being processed. In general, in a single threaded environment, when a thread blocks because it is waiting for some resource, the entire program stops running.

The benefits of Java’s multithreading is that the main loop/pooling mechanism is eliminated. One thread can pause without stopping other parts of program. For example, the idle time created when a thread reads data from a network or waits for user input can be utilized elsewhere. Multithreading allows animation loops to sleep for a second between each frame without causing the whole system to pause. When a thread blocks in Java program, only the single thread that is blocked pauses. All other threads continue to run.

Advantages of multithreading

  • Creating a thread within existing process is cheaper than creating a process.
  • Switching to different thread within the same process is cheaper than switching between threads to different processes.
  • Thread within a process may share data and other resources conveniently and efficiently as compared to separate process.
  • The benefits of multithreading are better interactive responsiveness and real time behavior
  • Multithreading enables us to write very efficient programs that make maximum use of the CPU, because idle time can be kept to a minimum.

Synchronization

Because multithreading introduces an asynchronous behavior to our programs, there must be a way for us to enforce synchronicity when we need it. If example, if we want two threads to communicate and share a complicated data structure, such as linked list, we need some way to ensure that they don’t conflict with each other. That is, we must prevent one thread from writing data while another thread is in the middle of reading it. This problem is handled by Java using the concept of synchronized method and synchronized statement. Once a thread is inside a synchronized method, no other thread can call any other synchronized method on the same object. This enables us to write very clear and concise multithreaded code, because synchronization support is built into the language.

Messaging

 After we divide our program into separate threads we need to define how they will communicate with each other. When programming with most other languages, we must depend on the operating system to establish communication between threads. This, of course, adds overhead. By contrast, Java provides a clean, low cost way for two or more threads to talk to each other, via calls to predefined methods that all objects have. Java’s messaging system allows a thread to enter a synchronized method on an object, and then wait there until some other thread explicitly notifies it to come out.

The Thread Class and the Runnable Interface

Java’s multithreading system is built upon the Thread class, its methods, and its companion interface, Runnable. Thread encapsulates a thread of execution. Since we can’t directly refer to the ethereal state of a running thread, you will deal with it through its proxy, the Thread instance that spawned it. To create a new thread, our program will either extend Thread or implement the Runnable interface.

The Thread class defines a several methods that help manage threads. The ones that will be used here are as follows:

Method Meaning
getName Obtain a name thread’s name
getPriority Obtain a thread’s priority
isAlive Determine if a thread is still running
Join Wait for a thread to terminate
Run Entry point for the thread
Sleep Suspend a thread for a period of time
Start Start a thread by calling its run method.

Life Cycle of a Thread

During the life cycle of a thread, there are many states it can enter. They include:

  • New born state
  • Runnable state
  • Running state
  • Blocked State
  • Dead State

New Born State: When we create a thread object, the thread is born and it is said to be in new born state. The thread is not scheduled for running. At this state, we can do only one of the following things with it.

  • Schedule it for running using the start() method.
  • Kill it using stop () method.

If it is scheduled, it moves to runnable state.

Runnable State: The runnable state means that the thread is ready for execution and is waiting for the availability of the processor. That is, the thread has joined the queue for threads that are waiting for execution. If all the threads have equal priority, then they are given time slots for execution is round robin fashion.

Running State: Running means that the process has given its time to the thread for its execution. The thread runs until it relinquishes control on its own or a higher priority thread preempts it. A running thread may relinquish its control in one of the following situations.

  • It has been suspended using suspend() method.
  • It has been made to sleep using sleep() method.
  • It has been to wait until some event occurs. That is done using wait() method.

Blocked State:  A thread is said to be blocked when it is prevented from entering into runnable state and subsequently the running state. This happens when the thread is suspended, sleeping or waiting inorder to satisfy certain requirements.

Dead State: A running thread ends its life when it has completed executing its run () method. It is a natural death. However, we can kill it by calling the stop () method to any state. A thread can be killed as soon as it is born or while it is running, even when it is in blocked state.

Daemon Thread: There are two kinds of threads, daemon threads and user threads. A daemon thread is simply a thread that has no other role in life to serve others. Examples are: timer threads that send regular “time ticks” to other threads. A daemon thread can be set via setDaemon (boolean on) method.

The Main Thread

When the java program starts up, one thread begins running immediately. This is usually called the main thread. The main thread is important for two reasons:

  • It is the thread from which other “child” threads will be spawned.
  • Often, it must be the last thread to finish execution because it performs various shutdown actions.

The main thread can be controlled through Thread object. To do so, we must obtain a reference to it by calling the method currentThread (), which is a public static member of Thread. Its general form is:

static Thread currentThread()

This method returns a reference to the thread in which it is called. Once we have a reference to the main thread, we can control it just like any other thread. Let us consider the following program:

public class CurrentThreadDemo {

    public static void main(String[] args) {

        Thread t = Thread.currentThread();

        System.out.println("The current thread is"+t);

        t.setName("My Thread");

        System.out.println("After the name change"+t);

        try{

            for(int n=5;n>0;n--){

                System.out.print(n+ " ");

                Thread.sleep(1000);

            }

        }catch(InterruptedException e){

            System.out.println("Main thread Interrupted");

        }

    }           

}

 

The output of the above program is:

The current thread isThread[main,5,main]

After the name changeThread[My Thread,5,main]

5   4   3  2  1

The pause is accomplished  by the sleep method. The argument to sleep() specifies the delay period in milliseconds. The sleep() method in Thread might throw an InterruptedException. This would happen is some other thread wanted to interrupt this sleeping one. The println() prints name of the thread, its priority, which is default value, and name its group. By default, the name of the thread is main. Its priority is 5 and main is also the name of the group of threads to which this thread belongs. A thread is a data structure that controls the state of a collection of threads as a whole.

Creating a Thread

There are two ways to create a thread.

  • By extending the Thread class
  • By implementing the Runnable interface

Creating a thread by extending the Thread class :  The Thread class of the java.lang package is used to create and control threads. To create a thread, a new instance of this class must be created. The thread class has seven constructors.

Thread()

Thread(Runnable)

Thread(ThreadGroup)

Thread(String),

Thread(ThreadGroup, String)

Thread(Runnable, String)

Thread (ThreadGroup, Runnable, String)

Some of the methods that control the thread execution are the following:

  • start (): This method starts the thread. It starts executing in the run () method of its runnable target that was set when the constructor was called. The method can be called only once.
  • suspend (): This method suspends the execution of the thread. It remains suspended until resume() is called.
  • resume (): This method resumes the execution of a suspended thread. It has no effect on a thread that is not suspended.
  • stop () : This method stops and kills a running thread.
  • sleep (int m) :The thread sleeps for m milliseconds.

The following program illustrates the creation of multiple threads in a program.

class ThreadA extends Thread{

    public void run(){

        for(int i=0;i<15;i++){

            System.out.println("From thread A:i="+i);

        }

        System.out.println("Exit from A");

    }

}

class ThreadB extends Thread{

    public void run(){

        for(int j=0;j<10;j++){

            System.out.println("From thread B:j="+j);

        }

        System.out.println("Exit from B");

    }

}

class ThreadC extends Thread{

    public void run(){

        for(int k=0;k<5;k++){

            System.out.println("From thread C:k="+k);

        }

        System.out.println("Exit from C");

    }

}

public class MultiThreadDemo {

    public static void main(String[] args) {

        System.out.println("Start of main class");

        ThreadA a = new ThreadA();

        a.start();

        ThreadB b = new ThreadB();

        b.start();

        ThreadC c = new ThreadC();

        c.start();

    }

}

 

The output of the above program is

Start of main class

From thread A:i=0

From thread A:i=1

From thread A:i=2

From thread A:i=3

From thread A:i=4

From thread A:i=5

From thread A:i=6

From thread A:i=7

From thread A:i=8

From thread A:i=9

From thread A:i=10

From thread A:i=11

From thread A:i=12

From thread A:i=13

From thread A:i=14

Exit from A

From thread B:j=0

From thread B:j=1

From thread B:j=2

From thread B:j=3

From thread B:j=4

From thread C:k=0

From thread B:j=5

From thread C:k=1

From thread B:j=6

From thread B:j=7

From thread B:j=8

From thread C:k=2

From thread B:j=9

From thread C:k=3

Exit from B

From thread C:k=4

Exit from C

Creating a thread by implementing the Runnable Interface

It is an another technique used to create the threads. If a thread is to start running in the applet, it must use the Runnable interface. The applet cannot inherit from both the Thread  and Applet classes so we must implement Runnable interface. There isn’t much difference between the two approaches. Both extending the Tread class and implementing the Runnable interface have the same functionality.

The following program illustrates the implementation of Runnable to create threads.

class ThreadX implements Runnable {

    public void run(){

        for(int i=0;i<15;i++){

            System.out.println("From thread A:i="+i);

        }

        System.out.println("Exit from A");

    }

}

class ThreadY implements Runnable{

    public void run(){

        for(int j=0;j<10;j++){

            System.out.println("From thread B:j="+j);

        }

        System.out.println("Exit from B");

    }

}

class ThreadZ implements Runnable{

    public void run(){

        for(int k=0;k<5;k++){

            System.out.println("From thread C:k="+k);

        }

        System.out.println("Exit from C");

    }

}

public class RunnableTest {

    public static void main(String[] args) {

        System.out.println("Start of main class");

        ThreadX a = new ThreadX();

        ThreadY b = new ThreadY();

        ThreadZ c = new ThreadZ();

        a.run();

        b.run();

        c.run();       

    }

}

 

The output of the above program is

Start of main class

From thread A:i=0

From thread A:i=1

From thread A:i=2

From thread A:i=3

From thread A:i=4

From thread A:i=5

From thread A:i=6

From thread A:i=7

From thread A:i=8

From thread A:i=9

From thread A:i=10

From thread A:i=11

From thread A:i=12

From thread A:i=13

From thread A:i=14

Exit from A

From thread B:j=0

From thread B:j=1

From thread B:j=2

From thread B:j=3

From thread B:j=4

From thread B:j=5

From thread B:j=6

From thread B:j=7

From thread B:j=8

From thread B:j=9

Exit from B

From thread C:k=0

From thread C:k=1

From thread C:k=2

From thread C:k=3

From thread C:k=4

Exit from C

Creating Multiple thread

Our program can spawn as many threads as we need. let us consider the following program:

class NewThread implements Runnable{

    String name;

    Thread t;

    public NewThread(String threadName){

        name = threadName;

        t = new Thread(this, name);

        t.start();

    }

    public void run(){

        try{

            for(int i=5;i>0;i--){

                System.out.println(name+" :"+i);

                Thread.sleep(1000);

            }

        }catch(InterruptedException e){

            System.out.println(name +"is interrupted ");

        }

        System.out.println(name+ " existing. ");

    }

}

public class MultiThreadDemo {

    public static void main(String[] args) {

        new NewThread("One");

        new NewThread("Two");

        new NewThread("Three");

        try{

            Thread.sleep(10000);

        }catch(InterruptedException e){

            System.out.println("Main thread intrrupted ");

        }

        System.out.println("Main thread existing");

    }   

}

 

The output of the above program:

One :5

Three :5

Two :5

One :4

Two :4

Three :4

One :3

Two :3

Three :3

One :2

Two :2

Three :2

Two :1

Three :1

One :1

Three existing.

One existing.

Two existing.

Main thread existing

Using isLive() and join()

Two ways exists to determine whether a thread has finished. First, we can call isLive() on the thread. This method is defined by Thread, and its general form is shown here:

final boolean isLive()

The isLive() method returns true if the thread upon which is called is still running. It returns false otherwise.

The isLive() is occasionally useful, the method that we more commonly use to wait for a thread to finish is called join(), shown here.

final void join() throws InterruptedException

The method waits until the thread on which it is called terminates. Its name comes from the concept of the calling thread waiting until the specified joins it. Additional form of join() allows you to specify a maximum amount of time that you want to wait for the specified thread to terminate.

Let us consider the following program:

class ThreadA extends Thread{

    public void run(){

        for(int i=0;i<15;i++){

            System.out.println("From thread A:i="+i);

        }

        System.out.println("Exit from A");

    }

}

class ThreadB extends Thread{

    public void run(){

        for(int j=0;j<10;j++){

            System.out.println("From thread B:j="+j);

        }

        System.out.println("Exit from B");

    }

}

class ThreadC extends Thread{

    public void run(){

        for(int k=0;k<5;k++){

            System.out.println("From thread C:k="+k);

        }

        System.out.println("Exit from C");

    }

}

public class MultiThreadDemo {

    public static void main(String[] args) {

        System.out.println("Start of main class");

        ThreadA a = new ThreadA();       

        ThreadB b = new ThreadB();      

        ThreadC c = new ThreadC();       

        System.out.println("The ThreadA started");

        a.start();      

        System.out.println("The ThreadB started");

        b.start();

        System.out.println("The threadC started");

        c.start();

        System.out.println("Is is still live "+c.isAlive());

        try{

        a.join();

        b.join();

        c.join();

        }

        catch(InterruptedException e){

            System.out.println("Enterrupt occured");

        }

        System.out.println("End of main");       

    }

 

The output of the above program is:

Start of main class

The ThreadA started

The ThreadB started

The threadC started

From thread A:i=0

From thread A:i=1

From thread A:i=2

From thread A:i=3

From thread A:i=4

From thread B:j=0

From thread B:j=1

From thread C:k=0

From thread A:i=5

Is is still live true

From thread A:i=6

From thread C:k=1

From thread B:j=2

From thread C:k=2

From thread A:i=7

From thread C:k=3

From thread B:j=3

From thread C:k=4

Exit from C

From thread A:i=8

From thread A:i=9

From thread B:j=4

From thread A:i=10

From thread B:j=5

From thread A:i=11

From thread B:j=6

From thread A:i=12

From thread B:j=7

From thread A:i=13

From thread B:j=8

From thread A:i=14

From thread B:j=9

Exit from A

Exit from B

End of main

 

Thread Priorities

The thread priority is used by the thread scheduler to decide when each thread should be allowed to run. Every java thread has a priority that helps the OS determine the order in which threads are scheduled. Java priorities are in the range between MIN_PRIORITY (equal to 1) and MAX_PRIORITY (equal to 10). Threads with a higher priority are more important to a program and should be allocated processor time before lower priority threads. However, the thread priority cannot guarantee the order in which threads execute. By default, the priority of thread is set NORM_PRIORITY (a constant value of 5). Each new thread inherits the priority of the thread that created it.

A thread’s priority is used to decide when to switch from one running thread to the next. This is called a context switch. The rules that determine when a context switch takes place are simple:

  • A thread can voluntarily relinquish control. This is done by explicitly yielding, sleeping, or blocking on pending I/O. In this scenario, all other threads are examined, and the highest priority thread that is ready is given the CPU.
  • A thread can be preempted by a higher priority thread. In this case, a lower priority thread that does not yield the processor is simply preempted—no matter what it is doing by a higher priority thread. Basically, as soon as a higher priority thread wants to run, it does. This is called preemptive multitasking.

To set priority of thread, we use setPriority() method. The general form is:

final void setPriority(int level);

To get the current priority of the thread we use getPriority() method. The general form is:

final int getPriority();

The following program illustrates the concept of thread priorities:

class ThreadA extends Thread{

    public void run(){

        for(int i=0;i<15;i++){

            System.out.println("From thread A:i="+i);

        }

        System.out.println("Exit from A");

    }

}

class ThreadB extends Thread{

    public void run(){

        for(int j=0;j<10;j++){

            System.out.println("From thread B:j="+j);

        }

        System.out.println("Exit from B");

    }

}

class ThreadC extends Thread{

    public void run(){

        for(int k=0;k<5;k++){

            System.out.println("From thread C:k="+k);

        }

        System.out.println("Exit from C");

    }

}

public class MultiThreadDemo {

    public static void main(String[] args) {

        System.out.println("Start of main class");

        ThreadA a = new ThreadA();       

        ThreadB b = new ThreadB();      

        ThreadC c = new ThreadC();

        a.setPriority(Thread.MAX_PRIORITY);

        b.setPriority(Thread.NORM_PRIORITY);

        c.setPriority(Thread.MIN_PRIORITY);

        System.out.println("The priority of Thread A is :"+a.getPriority());

        System.out.println("The priority of Thread B is :"+b.getPriority());

        System.out.println("The priority of Thread C is :"+c.getPriority());

        System.out.println("The Thread A started");

        a.start();

        System.out.println("The Thread B started");

        b.start();

        System.out.println("The thread C started");

        c.start();

       

    }

}

 

The output of the program is:

Start of main class

The priority of ThreadA is :10

The priority of ThreadB is :5

The priority of ThreadC is :1

The Thread A started

The Thread B started

From threadA:i=0

From threadA:i=1

From threadA:i=2

From threadA:i=3

From threadA:i=4

From threadA:i=5

The threadC started

From threadA:i=6

From threadA:i=7

From threadB:j=0

From threadB:j=1

From threadA:i=8

From threadA:i=9

From threadA:i=10

From threadB:j=2

From threadA:i=11

From threadB:j=3

From threadA:i=12

From threadB:j=4

From threadA:i=13

From threadB:j=5

From threadC:k=0

From threadA:i=14

Exit from A

From threadC:k=1

From threadC:k=2

From threadB:j=6

From threadB:j=7

From threadC:k=3

From threadC:k=4

Exit from C

From threadB:j=8

From threadB:j=9

Exit from B

Synchronization

When two or more threads need to access to a shared resource, they need some way to ensure that resource will be used by only one thread at a time. The process by which this is achieved is called synchronization. The key to synchronization is the concept of monitor. The synchronization enables mechanism for accessing the resource only one thread at a time.

  • It is used to prevent a race condition; access to the shared object must be properly synchronized.
  • Each thread accessing the thread object excludes all other threads from accessing the object simultaneously. This is the process known as mutual exclusion.

We can synchronize out code in either of the two ways.

  • Using Synchronized methods.
  • Using Synchronized Statement.

Using Synchronized Methods

The synchronized method is accessible for only one thread at a time. To make a method synchronized, simply add the synchronized keyword to its declaration.

public synchronized void call (){

……………………………………………..

……………………………………………..}

Let consider the following example:

class Table{ 

 synchronized void printable (int n){

   for(int i=1;i<=10;i++){ 

     System.out.println (n*i+" "); 

     try{ 

      Thread.sleep(400); 

     }catch(Exception e){System.out.println(e);} 

   }   

 } 

} 

class ThreadA extends Thread{ 

Table t; 

ThreadA(Table t){ 

this.t=t; 

} 

public void run(){ 

t.printTable(5); 

}

} 

class ThreadB extends Thread{ 

Table t; 

ThreadB(Table t){ 

this.t=t; 

} 

public void run(){ 

t.printTable(100); 

} 

} 




public class SynchronizationDemo { 

public static void main (String args[]){ 

Table obj = new Table ();

ThreadA t1=new ThreadA(obj); 

ThreadB t2=new ThreadB (obj); 

t1.start (); 

t2.start (); 

} 

}

 

Using synchronized Statement

Another way to create synchronized code is with synchronized statements. Unlike synchronized methods, synchronized statements must specify the object that provides the intrinsic lock. The general form of statement synchronization is as follows:

Synchronized (Object){

………………………………….

}

class Table{ 

  void printTable(int n){

   for(int i=1;i<=10;i++){ 

     System.out.print(n*i+" "); 

     try{ 

      Thread.sleep(400); 

     }catch(Exception e){System.out.println(e);} 

   }   

 } 

} 

class ThreadA extends Thread{ 

Table t; 

ThreadA(Table t){ 

this.t=t; 

} 

public void run(){ 

    synchronized(t){

t.printTable(5); 

}

}

}

class ThreadB extends Thread{ 

Table t; 

ThreadB(Table t){ 

this.t=t; 

} 

public void run(){

synchronized(t){   

t.printTable(90); 

} 

} 

} 

public class SynchronizationDemo{ 

public static void main(String args[]){ 

Table obj = new Table();

ThreadA t1=new ThreadA(obj); 

ThreadB t2=new ThreadB(obj); 

t1.start(); 

t2.start(); 

} 

}

 

 

Interthread Communication

The use of wait(), notify() and notifyAll() methods

Java includes an elegant interprocess communication mechanism via wait (), notify (), notifyAll () methods which are final methods of the object class so all classes have them. All three methods can be called only from within a synchronized context. These methods are to help threads wait for a condition and notify other threads of when that condition changes.

  • The wait() tells the calling thread to give up the monitor and go to sleep until some other thread enters the same the same monitor and calls notify().
  • The notify() wakes up a thread that called wait on the same object.
  • The notifyall() wakes up the threads that calls wait() on the same object, One of the threads will be granted access

Following are the various forms of wait() defined by Object.

final void wait() throws InterruptedException.

final void wait(long millis) throws InterruptedException

final void wait(long millis, int nanos) throws InterruptedException.

The first form waits until notified. The second from waits until notified or until the specified period of milliseconds has expired. The third form allows us to specify the wait period in terms of milliseconds and nanoseconds.

The general form of notify () and notifyAll () are:

final void notify()

final void notifyAll()

A call to notify() resumes one waiting thread. A call to notifyAll() notifies all threads, with the highest priority thread gaining access to the Object.

Suspending, resuming and stopping threads

Sometimes, suspending execution of a thread is useful. For example, a separate thread can be used to display the time of a day. If the user does not want a clock, then its thread can be suspended. Whatever the case, suspending a thread is a simple matter. Once suspended, restarting the thread is also a simple matter.

We use suspend() and resume() methods defined by Thread, to pause and restart the execution of a thread. They have the following form:

final void suspend ()

final void resume ()

The Thread class also defines a method called stop() that stops a thread. Its signature is shown here:

final void stop()

Once a thread has stopped, it cannot be restarted using resume().

While the suspend (), resume(), and stop() methods defined by Thread seem to be a perfectly reasonable and convenient approach for managing the execution of threads, they must not be used for new Java programs. Here is why. The suspended() method of the Thread class was deprecated by Java 2 several years ago. This was done because suspend() can sometimes cause serious system failures. Assume that a thread has obtained locks on critical data structures. If that thread is suspended at that point, those locks are not relinquished. Other threads may be waiting for those resources can be deadlocked.

The resume() method is also deprecated. It does not cause problems, but cannot be used without the suspend() as its counterpart.

The stop() method of the Thread class, too, was deprecated by Java 2. This was done because this method can sometimes cause serious system failures. Assume that a thread is writing to a critically important data structure and has completed only part of its changes. If that thread is stopped at point, that data structure might be left in a corrupted state.

Let us consider an example consumer and producer. In this example, a producer produces a message via getMessage () method that is to be consumed by the consumer via getMessage() method, before it can produce the next message. It is so called producer consumer pattern, one thread can suspend using wait() and release the lock until the time when another thread wakens it using notify() or notifyAll().

Let us consider the following program

class MessageBox {

    private String message;

    private boolean hasMessage;

    public synchronized void putMessage(String message){

        while(hasMessage){

            try{

                wait();

            }catch(InterruptedException e){}

        }

        hasMessage = true;

        this.message = message+"[email protected] "+System.nanoTime();

        notify();

    }

    public synchronized String getMessage(){

        while(!hasMessage){

            try{

                wait();

            }catch(InterruptedException e){}

        }

        hasMessage = false;

        notify();

        return message+"Get @"+System.nanoTime();

    }

}

public class TestMessageBox {

    public static void main(String [] args){

        final MessageBox box = new MessageBox();

        class ProducerThread extends Thread{       

            public void run(){

                System.out.println("Producer thread started...");

                for(int i=1;i<=6;i++){

                    box.putMessage ("message "+i);

                    System.out.println("Put message "+i);

                }

            }

        }

        class ConsumerThread extends Thread{

            public void run(){

                System.out.println("consumer thread 1 started");

                for(int i=1;i<=6;i++){

                System.out.println("Consumer thread1 get "+box.getMessage());

                }

            }

        }

        ConsumerThread t1 = new ConsumerThread();      

        ProducerThread t2 = new ProducerThread();

        t1.start();

        t2.start();

    }   

}

 

The output of the above program is:

consumer thread 1 started

Producer thread started…

Put message 1

Consumer thread1 get message [email protected] 126028633908589Get @126028634023766

Put message 2

Consumer thread1 get message [email protected] 126028634036880Get @126028634111004

Put message 3

Consumer thread1 get message [email protected] 126028634165171Get @126028634248988

Put message 4

Consumer thread1 get message [email protected] 126028634316839Get @126028634382410

Put message 5

Consumer thread1 get message [email protected] 126028634440569Get @126028634499297

Put message 6

Consumer thread1 get message [email protected] 126028634553465Get @126028634616185The output message may appear out of order. But close inspection on the put/get timestamp confirms the correct sequence of operations.

The synchronized producer method putMessage () acquires the lock of this object, check if the previous message has been cleared. Otherwise, it calls wait(), release the lock of this object, goes into WAITING state and places this thread on this object’s “wait” set. On the other hand, the synchronized consumer’s method getMessage () acquires the lock of this object and checks for new message. If there is a new message, it clears the message and issues notify(), which arbitrarily picks a thread on this object’s wait set (which happens to be the producer thread in this case) and places it on BLOCKED state. The consumer thread, in turn, goes into the WAITING state and placed itself int the “wait” set of this object (after the wait() method). The producer thread then acquires the thread and continues its operations.

The difference between notify () and notifyAll () is that the notify () arbitrarily picks a thread from this object’s waiting pool and places it on the seeking lock state, while notifyall () awakens all the threads in this object’s waiting pool. The waken thread then compete for execution in the normal manner.

Leave a Reply

Your email address will not be published. Required fields are marked *