Step 3 of 3: Sharing and Synchronization

Threads running concurrently can share resources like variables and data, and can manipulate the file etc.  So due to this, Synchronization among threads becomes a key issue. The best way to illustrate the Synchronization and Locking is through a simple code.  Let us consider a day to day example of Airline ticketing, where the issuer allocates the ticket in a ticketpool, and the travel agents collect the ticket from this ticketpool. Synchronization between the actual allocation of tickets and collection is very much needed. The source code for issuer.java is as follows:

import java.awt.*;

class issuer extends Thread 
{

        private Ticketpool ticketpool;
        private int ticket_no;
        private int i;

        public issuer( Ticketpool tp, int n)
        {
           ticketpool = tp;
           this.ticket_no = n;
        }

        public void run()
        {

           for( i=0; i< 5 ; i++)
           {
              ticketpool.allocate(i);
              System.out.println("Issuer has issued " + i );
              try{
                     sleep((int)(Math.random() * 100));
                 }
                 catch(Exception e)
                 {
                   
    System.out.println(e.getMessage());
                 }
           }
        } 

The code starts with creating a object of type Ticketpool, call it ticketpool, which is the common object shared between the issuer and the travel agent. The run() method has a for loop which allocates the ticket to the ticketpool object via the method allocate()Sleep() function is used so that the thread gets temporarily in a non-runnable mode which allows other thread to execute. Now consider a class Travelagent, which collects the ticket from the ticketpool, whose source code is as follows.
import java.awt.*;

class travelagent extends Thread 
{
   private Ticketpool ticketpool;
   private int travelagent_no;
   private int i, ticket_no;

   public travelagent( Ticketpool tp, int n)
   {
         ticketpool = tp;
         this.travelagent_no = n;
   }
   public void run()
   {
         int value = 0;
         for( int i = 0; i < 5 ; i++)
            {

               value = ticketpool.collect();
               System.out.println("Travel Agent " + this.travelagent_no + "collects" + value );
            }
   }
}

Again the program makes an object of type Ticketpool and it overrides the run() method which does the job of collecting the tickets from the ticketpool through a method collect().  Now let us concentrate on the Ticketpool code as follows:
import java.awt.*;

class Ticketpool extends Thread 
{
   private boolean ticket_position = false;
   private int position;

   public synchronized int collect()
   {
      while ( ticket_position ==false )
      {

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

         ticket_position = false;
         notifyAll();
         return position;

   }
   public synchronized void allocate(int tno)
   {
      while ( ticket_position == true)
      {

         try{
              wait();
         }
         catch( Exception e)
         {
              System.out.println(e.getMessage());
         }
      }
         position = tno;
         ticket_position = true;
         notifyAll();


    }

}

In Java, the code segment that is used by different threads are called Critical Sections. In Java the critical sections have to be grouped into different methods, and are differentiated from the normal methods with the keyword synchronized. Java allocates a single lock with the object that has the synchronized keyword. The Ticketpool class has two methods that are synchronized. Hence each of the issuer and the travel agent who instances the Ticketpool object has a unique lock. Hence when the issuer issues the ticket by allocate() method of the Ticketpool, the lock remains with it and hence the travel agent cannot access this object. Similarly when the Travelagent accesses the collect() method the lock remains with it, and the issuer threads cannot access it. Note that the handing over of the lock (acquiring the monitors for the threads), and it's release is totally controlled by the runtime system. Also note that in Java the monitors are reentrant, i.e. a method can call another synchronized method and so can re-acquire the monitor.

The collect() method waits till ticket_position is true which occurs when the issuer issues the ticket through the allocate() method. The wait is used for temporary release of the control from the thread, sleep could have also been used.The notifyAll method wakes up all threads waiting on the object in question (in this case, the CubbyHole). The awakened threads compete for the lock. One thread gets it, and the others go back to waiting. Finally, source code for the ticket.java is included below.

import java.awt.*;

public class ticket 
{
     public static void main(String[] argv)
     {
          Ticketpool tpl;
          travelagent ta;
          issuer i;
          tpl = new Ticketpool();
          ta = new travelagent(tpl,1);
          i = new issuer(tpl, 1);

          i.start();
          ta.start();
     } 
}

The code mentioned above just makes an object of all the classes discussed so far and then calls the start() method of the two Thread's, which as discussed earlier calls run() method along with other housekeeping functions.

Program result
The result of the program are shown below.

Issuer has issued 0
Travel Agent 1collects0
Issuer has issued 1
Travel Agent 1collects1
Issuer has issued 2
Travel Agent 1collects2
Issuer has issued 3
Travel Agent 1collects3
Issuer has issued 4
Travel Agent 1collects4

This was a basic example illustrating various fundamentals of Java Multithreading. Using these, various interesting applications can be built.