Skip to content

7. Threads

7.1. Introduction

When an application is launched, it runs in an execution flow called a thread. The class that models a thread is the java.lang.Thread class, which has the following properties and methods:

currentThread()
returns the thread currently running
setName()
sets the name of the thread
getName()
thread name
isAlive()
indicates whether the thread is active (true) or not (false)
start()
starts the execution of a thread
run()
method executed automatically after the preceding start method has been executed
sleep(n)
pauses the execution of a thread for n milliseconds
join()
blocking operation—waits for the thread to finish before proceeding to the next instruction

The most commonly used constructors are as follows:

Thread()
creates a reference to an asynchronous task. This task is still inactive. The created task must have a run method: most often, a class derived from Thread will be used.
Thread(Runnable object)
Same as above, but the Runnable object passed as a parameter implements the run method.

Let’s look at a simple application demonstrating the existence of a main execution thread—the one in which a class’s main function runs:

// using threads

import java.io.*;
import java.util.*;

public class thread1{
    public static void main(String[] arg) throws Exception {
        // Initialize current thread
        Thread main = Thread.currentThread();
        // display
        System.out.println("Current thread: " + main.getName());
        // change the name
        main.setName("myMainThread");
        // verification
        System.out.println("Current thread: " + main.getName());

        // infinite loop
        while(true){
       // get the time
      Calendar calendar = Calendar.getInstance();
      String H = calendar.get(Calendar.HOUR_OF_DAY) + ":"
      +calendar.get(Calendar.MINUTE)+":"
      +calendar.get(Calendar.SECOND);
            // display
            System.out.println(main.getName() + " : " + H);
            // temporary pause
            Thread.sleep(1000);
        }//while
    }//main
}//class

Screen output:


Current thread: main
Current thread: myMainThread
myMainThread: 15:34:9
myMainThread: 15:34:10
myMainThread: 15:34:11
myMainThread: 3:34:12 PM
Terminate the command program (Y/N)? y

The previous example illustrates the following points:

  • the main function runs correctly in a thread
  • we can access this thread's properties via Thread.currentThread()
  • the role of the sleep method. Here, the thread executing main sleeps for 1 second between each display.

7.2. Creating execution threads

It is possible to have applications where pieces of code execute "simultaneously" in different execution threads. When we say that threads run simultaneously, we are often using the term loosely. If the machine has only one processor, as is still often the case, the threads share this processor: they each have access to it, in turn, for a brief moment (a few milliseconds). This is what creates the illusion of parallel execution. The amount of time allocated to a thread depends on various factors, including its priority, which has a default value but can also be set programmatically. When a thread has the processor, it normally uses it for the entire time allotted to it. However, it can release it early:

  • by waiting for an event (wait, join)
  • by sleeping for a specified period (sleep)
  • A thread T can be created in various ways
    • by extending the Thread class and overriding its run method.
    • by implementing the Runnable interface in a class and using the new Thread(Runnable) constructor. Runnable is an interface that defines only a single method: public void run(). The argument of the preceding constructor is therefore any class instance implementing this run method.

In the following example, threads are created using an anonymous class that extends the Thread class:

            // Create thread i
            tasks[i] = new Thread() {
          public void run() {
            display();
        }
      };//define tasks[i]

The run method here simply calls the display method.

  • The execution of thread T is started by T.start(): this method belongs to the Thread class and performs a number of initializations before automatically invoking the run method of the Thread or the Runnable interface. The program executing the T.start() statement does not wait for task T to finish: it immediately proceeds to the next statement. We then have two tasks running in parallel. They often need to be able to communicate with each other to know the status of the shared work to be done. This is the problem of thread synchronization.
  • Once started, the thread runs autonomously. It will stop when the run function it is executing has finished its work.
  • We can wait for thread T to finish executing using T.join(). This is a blocking instruction: the program executing it is blocked until task T has finished its work. It is also a means of synchronization.

Let’s examine the following program:

// using threads

import java.io.*;
import java.util.*;

public class thread2{
    public static void main(String[] arg) {
        // Initialize current thread
        Thread main = Thread.currentThread();
        // give the current thread a name
        main.setName("myMainThread");
        // Start of main
        System.out.println("Start of thread " + main.getName());

        // create execution threads
        Thread[] tasks = new Thread[5];
        for (int i = 0; i < tasks.length; i++) {
            // create thread i
            tasks[i] = new Thread() {
          public void run() {
            display();
        }
      };//define tasks[i]
            // Set the thread name
            tasks[i].setName(""+i);
            // start thread i
            tasks[i].start();
        }//for

        // end of main
        System.out.println("End of thread " + main.getName());
    }//Main

    public static void display() {
        // get the time
    Calendar calendar = Calendar.getInstance();
    String H = calendar.get(Calendar.HOUR_OF_DAY) + ":"
      +calendar.get(Calendar.MINUTE)+":"
      +calendar.get(Calendar.SECOND);
        // Display start of execution
        System.out.println("Start of method execution displayed in Thread " + 
        Thread.currentThread().getName() + " : " + H);
        // Sleep for 1 second
        try{
        Thread.sleep(1000);
    } catch (Exception ex) {}
    // Get the current time
    calendar = Calendar.getInstance();    
    H = calendar.get(Calendar.HOUR_OF_DAY) + ":"
      +calendar.get(Calendar.MINUTE)+":"
      +calendar.get(Calendar.SECOND);
        // Display end of execution
        System.out.println("End of method execution displayed in Thread " 
    +Thread.currentThread().getName()+ " : " + H);
    }// displays
}//class

The main thread, which executes the main function, creates 5 other threads responsible for executing the static display method. The results are as follows:


Start of thread myMainThread
Start of execution of the display method in Thread 0: 15:48:3
End of the myMainThread thread
Start of execution of the display method in Thread 1: 15:48:3
Start of execution of the affiche method in Thread 2: 15:48:3
Start of method execution in Thread 3: 15:48:3
Start of method execution displayed in Thread 4: 15:48:3
End of method execution displayed in Thread 0: 15:48:4
End of method execution displayed in Thread 1: 15:48:4
End of method execution displayed in Thread 2: 15:48:4
End of method execution displayed in Thread 3: 15:48:4
End of method execution displayed in Thread 4: 15:48:4

These results are very informative:

  • First, we see that starting a thread’s execution is not blocking. The main method started the execution of 5 threads in parallel and finished executing before them. The operation
            // we start the execution of thread i
            tasks[i].start();

starts the execution of the thread tasks[i], but once this is done, execution immediately continues with the next statement without waiting for the thread to finish.

  • All created threads must execute the display method. The order of execution is unpredictable. Even though in the example, the order of execution appears to follow the order in which the threads were launched, no general conclusions can be drawn from this. The operating system here has 6 threads and one processor. It will allocate the processor to these 6 threads according to its own rules.
  • The results show an effect of the sleep method. In the example, thread 0 is the first to execute the display method. The start-of-execution message is displayed, then it executes the sleep method, which suspends it for 1 second. It then loses the processor, which becomes available to another thread. The example shows that thread 1 will obtain it. Thread 1 will follow the same path as the other threads. When the 1-second sleep period for thread 0 ends, its execution can resume. The system grants it the processor, and it can complete the execution of the display method.

Let’s modify our program to end the *main* method with the following instructions:

        // end of main
        System.out.println("end of thread " + main.getName());
    // stop the application
    System.exit(0);

Running the new program yields:


Start of thread myMainThread
Start of method execution displayed in Thread 0: 16:5:45
Start of method execution in Thread 1: 16:5:45
Start of method execution in Thread 2: 16:5:45
Start of method execution in Thread 3: 16:5:45
End of thread myMainThread
Start of method execution in Thread 4: 16:5:45

As soon as the main method executes the statement:

    System.exit(0);

It stops all of the application's threads, not just the main thread. The main method might want to wait for the threads it created to finish executing before terminating itself. This can be done using the join method of the Thread class:

    // wait for all threads
        for(int i=0;i<tasks.length;i++){
            // wait for thread i
            tasks[i].join();
    }//for  

        // end of main
        System.out.println("End of thread " + main.getName());
    // Shut down the application
    System.exit(0);

This produces the following results:

Start of thread myMainThread
Start of method execution displayed in Thread 0: 16:11:9
Start of method execution in Thread 1: 16:11:9
Start of method execution in Thread 2: 16:11:9
Start of method execution in Thread 3: 16:11:9
Start of method execution in Thread 4: 16:11:9
End of method execution displayed in Thread 0: 16:11:10
End of method execution displayed in Thread 1: 16:11:10
End of method execution displayed in Thread 2: 16:11:10
End of method execution displayed in Thread 3: 16:11:10
End of method execution displayed in Thread 4: 16:11:10
End of the myMainThread thread

7.3. The Benefits of Threads

Now that we have highlighted the existence of a default thread—the one that executes the Main method—and we know how to create others, let’s consider the benefits of threads for us and why we are presenting them here. There is a type of application that lends itself well to the use of threads: client-server applications on the Internet. In such an application, a server located on machine S1 responds to requests from clients located on remote machines C1, C2, ..., Cn.

Image

We use Internet applications that follow this pattern every day: web services, email, forum browsing, file transfers... In the diagram above, server S1 must serve clients C1, C2, ..., Cn simultaneously. If we take the example of an FTP (File Transfer Protocol) server that delivers files to its clients, we know that a file transfer can sometimes take several hours. It is, of course, out of the question for a single client to monopolize the server for such a long period. What is usually done is that the server creates as many execution threads as there are clients. Each thread is then responsible for handling a specific client. Since the processor is cyclically shared among all active threads on the machine, the server spends a little time with each client, thereby ensuring the concurrency of the service.

Image

7.4. A graphical clock ap

Consider the following application, which displays a window with a clock and a button to stop or restart the clock:

For the clock to run, a process must update the time every second. At the same time, events occurring in the window must be monitored: when the user clicks the "Stop" button, the clock must be stopped. Here we have two parallel and asynchronous tasks: the user can click at any time.

Consider the moment when the clock has not yet been started and the user clicks the "Start" button. This is a classic event, and one might think that a method of the thread in which the window is running could then manage the clock. However, when a method of the GUI application is running, its thread is no longer listening for GUI events. These events occur and are placed in a queue to be processed by the application once the currently running method has finished. In our clock example, the method will always be running since only clicking the "Stop" button can stop it. However, this event will only be processed once the method has finished. We’re stuck in a loop.

The solution to this problem would be that when the user clicks the "Start" button, a task is launched to manage the clock, but the application can continue to listen for events occurring in the window. We would then have two separate tasks running in parallel:

  • clock management
  • listening for window events

Let’s return to our graphical clock:

No.
type
name
role
1
JTextField (Editable=false)
txtClock
displays the time
2
JButton
btnGoStop
stops or starts the clock

The source code for the application built with JBuilder is as follows:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

public class ClockInterface extends JFrame {
  JPanel contentPane;
  JTextField clockText = new JTextField();
  JButton btnGoStop = new JButton();

  // instance attributes
  boolean clockEnd = true;

  //Construct the frame
  public interface Clock() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  private void runClock(){
    // loop until told to stop
    while( ! clockEnd) {
      // get the time
      Calendar calendar = Calendar.getInstance();
      String H = calendar.get(Calendar.HOUR_OF_DAY) + ":"
      +calendar.get(Calendar.MINUTE)+":"
      +calendar.get(Calendar.SECOND);
      // display it in the T field
      txtClock.setText(H);
      // wait one second
      try{
        Thread.sleep(1000);
      } catch (Exception e){
        // exit with error
        System.exit(1);
      }//try
    }// while
  }// runClock

  // Initialize the component
  private void jbInit() throws Exception  {
...................
  }

  //Replaced, so we can exit when the window is closed
  protected void processWindowEvent(WindowEvent e) {
.............
  }

  void btnGoStop_actionPerformed(ActionEvent e) {
    // Start/stop the clock
    // retrieve the button label
    String label = btnGoStop.getText();
    // Start?
    if (label.equals("Start")) {
      // create the thread in which the timer will run
      Thread thClock = new Thread() {
        public void run(){
          runClock();
        }
      };//thread definition
      // allow the thread to start
      stopClock = false;
      // change the button label
      btnGoStop.setText("Stop");
      // start the thread
      thClock.start();
      // end
      return;
    }//if
    // stop
    if(label.equals("Stop")){
      // tell the thread to stop
      stopClock = true;
      // change the button label
      btnGoStop.setText("Start");
      // end
      return;
    }//if
  }
} 

When the user clicks the "Start" button, a thread is created using an anonymous class:

      Thread thHorloge = new Thread() {
        public void run(){
          runClock();
        }

The thread's run method calls the application's runHorloge method. Once this is done, the thread is launched:

      // we start the thread
      thClock.start();

The runHorloge method will then execute:

  private void runClock(){
    // loop until told to stop
    while( ! endClock){
      // get the current time
      Calendar calendar = Calendar.getInstance();
      String H = calendar.get(Calendar.HOUR_OF_DAY) + ":"
      +calendar.get(Calendar.MINUTE)+":"
      +calendar.get(Calendar.SECOND);
      // display it in the T field
      txtClock.setText(H);
      // wait one second
      try{
        Thread.sleep(1000);
      } catch (Exception e){
        // exit with error
        System.exit(1);
      }//try
    }// while
  }// runHorloge

The principle of the method is as follows:

  1. displays the current time in the txtHorloge text box
  2. pauses for 1 second
  3. resumes step 1, having first checked the boolean variable finHorloge, which will be set to true when the user clicks the Stop button.

7.5. A Clock applet

We convert the previous graphical application into an applet using the standard method and create the following HTML document, appletHorloge.htm:

<html>
  <head>
    <title>Clock Applet</title>
  </head>
  <body>
      <h2>A Clock Applet</h2>
    <applet
      code="appletHorloge.class"
      width="150"
      height="130"
    ></applet>
    </center>
  </body>
</html>

When we load this document directly into IE by double-clicking on it, we get the following display:

Image

In this example, all the elements required by the applet are in the same folder:

E:\data\serge\Jbuilder\horloge\1>dir
06/13/2002  12:17                3,174 appletHorloge.class
06/13/2002  12:17                  658 appletHorloge$1.class
06/13/2002  12:17                  512 appletClock$2.class
06/13/2002  12:20                  245 appletClock.htm

Our applet can be improved. We mentioned that when the applet loads, the init method is executed, followed by the start method if it exists. Additionally, when the user leaves the page, the stop method is executed if it exists. When the user returns to the applet’s page, the start method is called again. When an applet implements visual animation threads, the applet’s start and stop methods are often used to launch and stop the threads. It is indeed unnecessary for a visual animation thread to continue running in the background while the animation is hidden.

So we add the following start and stop methods to our applet:

  public void stop(){
    // the page is hidden
    // tracking
    System.out.println("Page stop");
    // the page is hidden - we stop the thread
    finHorloge = true;
  }

  public void start(){
    // the page reappears
    // tracking
    System.out.println("Page start");
    // restart a new clock thread if necessary
    if(btnGoStop.getText().equals("Stop")){
      // change the label
      btnGoStop.setText("Start");
      // and simulate a user click
      btnGoStop_actionPerformed(null);
    }//if
  }//start

Additionally, we added a check in the thread's run method to determine when it starts and stops:

  private void runClock(){
    // tracking
    System.out.println("Clock thread started");
    // loop until told to stop
    while( ! endClock){
      // Get the time
      Calendar calendar = Calendar.getInstance();
      String H = calendar.get(Calendar.HOUR_OF_DAY) + ":"
      +calendar.get(Calendar.MINUTE)+":"
      +calendar.get(Calendar.SECOND);
      // display it in the T field
      txtClock.setText(H);
      // wait one second
      try{
        Thread.sleep(1000);
      } catch (Exception e){
        // exit with error
        return;
      }//try
    }// while
    // monitoring
    System.out.println("Clock thread finished");
  }// runClock

Now we run the applet with AppletViewer:

E:\data\serge\Jbuilder\horloge\1>appletviewer appletHorloge.htm
Start page    // applet launched - page displayed
Clock thread launched    // the thread is launched accordingly
Page stop    // applet minimized
Clock thread finished    // the thread is stopped accordingly
Page start    // applet redisplayed
Clock thread started    // the thread is restarted
Clock thread finished    // Stop button pressed
Clock thread started    // Start button pressed
Stop page    // applet in icon    icon
Clock thread finished    // thread stopped accordingly
Page start    // Applet redisplayed
Clock thread started    // thread restarted accordingly

With AppletViewer, the start event occurs when the AppletViewer window is visible, and the stop event occurs when it is minimized. The results above show that when the HTML document is hidden, the thread is indeed stopped if it was active.

7.6. Task Synchronization

In our previous example, there were two tasks:

  • the main task represented by the application itself
  • the task responsible for the clock

Coordination between the two tasks was handled by the main task, which set a boolean to stop the clock thread. We will now address the problem of concurrent access by tasks to shared resources, a problem also known as "resource sharing." To illustrate this, we will first examine an example.

7.6.1. An unsynchronized counter

Consider the following graphical interface:

Image

No.
type
name
role
1
JTextField
txtAGenerate
specifies the number of threads to generate
2
JTextField
(non-editable)
txtGenerated
shows the number of threads generated
3
JTextField
(non-editable)
txtStatus
provides information about errors encountered and about the application itself
4
JButton
btnGenerate
starts thread generation

The application works as follows:

  • the user specifies the number of threads to generate in field 1
  • they start generating these threads using button 4
  • the threads read the value of field 2, increment it, and display the new value. Initially, this field contains the value 0.

The generated threads share a resource: the value of field 2. Here, we aim to demonstrate the problems encountered in such a situation. Here is an example of execution:

Image

We see that we requested the generation of 1,000 threads, but only 7 were counted. The relevant code for the application is as follows:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class interfaceSynchro extends JFrame {
  JPanel contentPane;
  JLabel jLabel1 = new JLabel();
  JTextField txtGenerate = new JTextField();
  JButton btnGenerate = new JButton();
  JTextField txtStatus = new JTextField();
  JTextField txtGenerated = new JTextField();
  JLabel jLabel2 = new JLabel();

  // instance variables
    Thread[] tasks = null;   // threads
    int[] counters = null;   // counters

  //Build the frame
  public interfaceSynchro() {
..........
  }

  //Initialize the component
  private void jbInit() throws Exception {
......................
  }

  //Replaced, so we can exit when the window is closed
  protected void processWindowEvent(WindowEvent e) {
..................
  }

  void btnGenerate_actionPerformed(ActionEvent e) {
    //generate threads

    // read the number of threads to generate
    int nbThreads=0;
    try{
      // Read the field containing the number of threads
      nbThreads = Integer.parseInt(txtAGénérer.getText().trim());
      // positive >
      if(nbThreads <= 0) throw new Exception();
    } catch (Exception ex) {
      // error
      txtStatus.setText("Invalid number");
      // start over
      txtGenerate.requestFocus();
      return;
    }//catch

    // Initially, no threads generated
    txtGenerated.setText("0");  // task counter set to 0

    // generate and launch the threads
    tasks = new Thread[nbThreads];
    counters = new int[nbThreads];
    for(int i=0;i<tasks.length;i++){
      // create thread i
      tasks[i] = new Thread() {
          public void run() {
          increment();
        }
      };//thread i
      // set its name
      tasks[i].setName(""+i);
      // start its execution
      tasks[i].start();
    }//for
  }//generate

  // increment
  private void increment(){
    // retrieve the thread number
    int iThread = 0;
    try{
      iThread = Integer.parseInt(Thread.currentThread().getName());
    } catch (Exception ex) {}
    // read the task counter value
    try{
      counters[iThread] = Integer.parseInt(txtGenerated.getText());
    } catch (Exception e){}
    // increment it
    counters[iThread]++;

    // wait 100 milliseconds—the thread will then lose the CPU
    try{
      Thread.sleep(100);
    } catch (Exception e){
      System.exit(0);
    }

    // display the new counter
    txtGenerated.setText("");
    txtGenerated.setText("" + counters[iThread]);
    // monitoring
    System.out.println("Thread " + iThread + ": " + counters[iThread]);
  }// increment

}// class

Let's break down the code:

  • The window declares two instance variables:
  // instance variables
    Thread[] tasks = null;   // the threads
    int[] counters = null;   // the counters

The tasks array will be the array of generated threads. The counters array will be associated with the tasks array. Each task will have its own counter to retrieve the value of the txtGenerated field from the GUI.

  • When the Generate button is clicked, the btnGenerate_actionPerformed method is executed.
  • This method begins by retrieving the number of threads to generate. If necessary, an error is reported if this number is not valid. It then generates the requested threads, taking care to record their references in an array and assigning a number to each one. The run method of the generated threads calls the increment method of the class. All threads are started (start). The array of counters associated with the threads is also created.
  • The increment method:
  • reads the current value of the txtGénérés field and stores it in the counter belonging to the currently running thread
  • pauses for 100 ms to intentionally idle the processor
  • displays the new value in the txtGenerated field

Let’s now explain why the thread count is incorrect. Suppose there are 2 threads to generate. They execute in an unpredictable order. One of them runs first and reads the value 0 from the counter. It then sets it to 1 but does not write " " to the window: it voluntarily pauses for 100 ms. It then loses the processor, which is then given to another thread. This thread operates in the same way as the previous one: it reads the window’s counter and retrieves the 0 that is still there. It sets the counter to 1 and, like the previous one, pauses for 100 ms. The processor is then allocated back to the first thread: this thread writes the value 1 to the window’s counter and terminates. The processor is now allocated to the second thread, which also writes 1. We end up with an incorrect result.

Where does the problem come from? The second thread read an incorrect value because the first thread was interrupted before it finished its task, which was to update the counter in the window. This brings us to the concept of a critical resource and a critical section in a program:

  • A critical resource is a resource that can be held by only one thread at a time. Here, the critical resource is counter 2 in the window.
  • A critical section of a program is a sequence of instructions in a thread’s execution flow during which it accesses a critical resource. We must ensure that during this critical section, it is the only one with access to the resource.

7.6.2. Synchronized counting by method

In the previous example, each thread executed the window’s increment method. The increment method was declared as follows:

    private void increment()

Now we declare it differently:

  // increment
  private synchronized void increment(){

The **synchronized** keyword means that only one thread at a time can execute the increment method. Consider the following notation:

  • the window object F that creates threads in btnGenerate_actionPerformed
  • two threads T1 and T2 created by F

Both threads are created by F and then launched. They will therefore both execute the F.run method. Suppose T1 arrives first. It executes F.run and then F.increment, which is a synchronized method. It reads the counter value 0, increments it, and then pauses for 100 ms. The processor is then handed over to T2, which in turn executes F.run and then F.increment. At this point, it is blocked because thread T1 is currently executing F.increment, and the synchronized keyword ensures that only one thread at a time can execute F.increment. T2 then loses the processor without having been able to read the counter’s value. After 100 ms, T1 regains the processor, displays the counter value 1, exits F.incremente and then F.run, and terminates. T2 then regains the processor and can now execute F.incremente because T1 is no longer executing this method. T2 then reads the counter value 1, increments it, and pauses for 100 ms. After 100 ms, it regains the processor, displays the counter value 2, and terminates as well. This time, the value obtained is correct. Here is a tested example:

Image

7.6.3. Counting synchronized by an object

In the previous example, access to the txtGénérés counter was synchronized by a method. If the window that creates the threads is called F, we can also say that the F.incremente method represents a resource that should only be used by a single thread at a time. It is therefore a critical resource. Synchronized access to this resource was ensured by the synchronized keyword:

    private synchronized void increment()

One could also say that the critical resource is the object F itself. This is stricter than when the critical resource is F.increment. Indeed, in the latter case, if a thread T1 executes F.increment, a thread T2 cannot execute F.increment but can execute another method of the object F, whether it is synchronized or not. In the case where the object F itself is the critical resource, when a thread T1 executes a synchronized section of this object, every other synchronized section of the object becomes inaccessible to other threads. Thus, if thread T1 executes the synchronized method F.increment, thread T2 will not be able to execute not only F.increment but also any other synchronized section of F, even if no other thread is using it. This is therefore a more restrictive method.

Let’s assume, then, that the window becomes the critical resource. We would then write:

  // increment
  private void increment(){
    // critical section
    synchronized(this){
      // retrieve the thread ID
      int iThread = 0;
      try{
        iThread = Integer.parseInt(Thread.currentThread().getName());
      } catch (Exception ex) {}
      // read the task counter value
      try{
        counters[iThread] = Integer.parseInt(txtGenerated.getText());
      } catch (Exception e){}
      // increment it
      counters[iThread]++;

      // wait 100 milliseconds - the thread will then lose the CPU
      try{
        Thread.sleep(100);
      } catch (Exception e){
        System.exit(0);
      }

      // display the new counter
      txtGenerated.setText("");
      txtGenerated.setText("" + counters[iThread]);
    }//synchronized
  }// increment

All threads use the this window to synchronize. At runtime, we get the same correct results as before. We can actually synchronize on any object known to all threads. Here is another method, for example, that gives the same results:

  // instance variables
    Thread[] tasks = null;   // the threads
    int[] counters = null;   // the counters
    Object synchro = new Object(); // a thread synchronization object

  // increment
  private void increment(){
    // critical section
    synchronized(synchro){
..............
    }//synchronized
  }//increment

The window creates an Object-type object that will be used for thread synchronization. This method is better than the one that synchronizes on the this object because it is less restrictive. Here, if a thread T1 is in the synchronized section of increment and a thread T2 wants to execute another synchronized section of the same this object but synchronized by an object other than synchro, it will be able to do so.

7.6.4. Event-based synchronization

This time, we use a boolean variable canPass to indicate to a thread whether or not it can enter a critical section. A version without synchronization might look like this:


while(!canPass);        // wait for peutPasser to become true
canPass = false;            // no other thread should enter
critical section;            // here the thread is alone
canPass=true;            // another thread can enter the critical section

The first statement, where a thread loops while waiting for canPass to become true, is inefficient: the thread unnecessarily occupies the processor. This is called active waiting. We can improve the code as follows:


while(!canPass){        // wait for canPass to become true
   Thread.sleep(100);    // pause for 100 ms
}
canPass = false;            // no other thread should enter
critical section;            // here the thread is alone
canPass=true;            // another thread can enter the critical section

The wait loop is better here: if the thread cannot pass, it sleeps for 100 ms before checking again whether it can pass or not. In the meantime, the processor will be allocated to other threads in the system.

Both of these methods are actually incorrect: they do not prevent two threads from entering the critical section at the same time. Suppose a thread T1 detects that canPass is true. It will then proceed to the next instruction where it sets canPass to false to block the other threads. However, it may very well be preempted at that moment, either because its processor time slice has expired, because a higher-priority task has requested the processor, or for some other reason. The result is that it loses the processor. It will regain it a little later. In the meantime, other tasks will obtain the processor, including perhaps a thread T2 that loops while waiting for peutPasser to become true. It too will discover that peutPasser is true (the first thread did not have time to set it to false) and will also enter the critical section. Which was not supposed to happen.

The sequence


while(! peutPasser){            // wait for peutPasser to become true
   try{
        Thread.sleep(100);    // pause for 100 ms
    } catch (Exception e) {}
}// while
canPass = false;                // no other thread should pass

is a critical section that must be protected by synchronization. Based on the previous example, we can write:


    synchronized(synchro){
        while(!canPass){            // wait for canPass to become true
            try{
                Thread.sleep(100);    // pause for 100 ms
            } catch (Exception e) {}
        } // while
        canPass = false;                // no other thread should pass
    }// synchronized
    Critical section;                    // here the thread is alone
    canPass=true;                    // another thread can enter the critical section

This example works correctly. We can improve it by avoiding the thread's semi-active wait while it regularly checks the value of the boolean canPass. Instead of waking up every 100 ms to check the state of canPass, it can go to sleep and request to be woken up when canPass is true. We write this as follows:


synchronized(synchro){
    if (!canPass) {
        try{
            synchro.wait();            // if we can't proceed, then we wait
        } catch (Exception e){
            
        }
    }
    canPass = false;            // no other thread should pass
}// synchronized

The synchro.wait() operation can only be performed by a thread that is the current "owner" of the synchro object. Here, it is the sequence:


synchronized(synchro){

}// synchronized

that ensures the thread owns the synchro object. Through the synchro.wait() operation, the thread relinquishes ownership of the synchronization lock. Why is this? Generally because it lacks the resources to continue working. So rather than blocking other threads waiting for the synchro resource, it relinquishes it and waits for the resource it lacks. In our example, it waits for the boolean canPass to become true. How will it be notified of this event? As follows:


synchronized(synchro){
    if (!canPass) {
        try{
            synchro.wait();            // if we can't pass, then we wait
        } catch (Exception e){
            
        }
    }
    canPass = false;            // no other thread should pass
}// synchronized
critical section...
synchronized(synchro){
      synchro.notify();
    }

Let’s consider the first thread to acquire the synchronization lock. Let’s call it T1. Suppose it finds the canPass boolean to be true, since it is the first thread. It then sets it to false. It then exits the critical section locked by the sync object. Another thread can then enter the critical section to check the value of canPass. It will find it false and will then wait for an event (wait). In doing so, it relinquishes ownership of the sync object. Another thread can then enter the critical section: it too will wait because canPass is false. We can therefore have multiple threads waiting for an event on the sync object.

Let’s return to thread T1, which has been granted access. It executes the critical section and then signals that another thread may now proceed. It does so with the following sequence:


synchronized(synchro){
      synchro.notify();
    }

It must first regain possession of the sync object using the synchronized statement. This shouldn’t be a problem since it’s competing with threads that, if they momentarily obtain the sync object, must release it via a wait because they find peutPasser to be false. So our thread T1 will eventually acquire ownership of the synchro object. Once this is done, it signals via the synchro.notify operation that one of the threads blocked by a synchro.wait must be woken up. It then relinquishes ownership of the sync object again, which is then given to one of the waiting threads. This thread continues its execution with the instruction following the wait that had put it on hold. In turn, it will execute the critical section and issue a synchro.notify to release another thread. And so on.

Let’s look at how this works using the counting example we’ve already studied.

  void btnGenerate_actionPerformed(ActionEvent e) {
    //thread generation

    // read the number of threads to generate
    int nbThreads=0;
    try{
      // read the field containing the number of threads
      nbThreads = Integer.parseInt(txtAGénérer.getText().trim());
      // positive >
      if(nbThreads <= 0) throw new Exception();
    } catch (Exception ex) {
      // error
      txtStatus.setText("Invalid number");
      // start over
      txtGenerate.requestFocus();
      return;
    }//catch

    // Reset task counter
    txtGenerated.setText("0");  // reset task counter to 0
    // 1st thread can proceed
    canProceed=true;
    // Generate and launch the threads
    tasks = new Thread[nbThreads];
    counters = new int[nbThreads];
    for(int i=0;i<tasks.length;i++){
      // create thread i
      tasks[i] = new Thread() {
          public void run() {
          synchronize();
        }
      };//thread i
      // set its name
      tasks[i].setName(""+i);
      // start its execution
      tasks[i].start();
    }//for
  }//generate

Now, the threads no longer execute the increment method but the following synchronize method:

  // thread synchronization step
  public void synchronize(){
    // request access to the critical section
    synchronized(synchro){
      try{
        // Can we proceed?
        if (!canPass) {
          System.out.println(Thread.currentThread().getName() + " waiting");
          synchro.wait();
        }
        // We have passed—we prevent other threads from passing
        canPass = false;
      } catch(Exception e){
        txtStatus.setText(""+e);
                    return;
      }//try
    }// synchronized

    // critical section
    System.out.println(Thread.currentThread().getName() + " passed");
    increment();

    // we're done - release any thread blocked at the entrance to the critical section
    canPass=true;
    System.out.println(Thread.currentThread().getName() + " finished");
    synchronized(synchro){
      synchro.notify();
    }// synchronized
  } // synchronized

The purpose of the synchronize method is to process threads one at a time. It uses a synchronization variable called synchro to do this. The increment method is no longer protected by the synchronized keyword:

  // increment
  private void increment(){
    // retrieve the thread number
    int iThread = 0;
    try{
      iThread = Integer.parseInt(Thread.currentThread().getName());
    } catch (Exception ex) {}
    // read the task counter value
    try{
      counters[iThread] = Integer.parseInt(txtGenerated.getText());
    } catch (Exception e){}
    // increment it
    counters[iThread]++;
    // wait 100 milliseconds—the thread will then lose the CPU
    try{
      Thread.sleep(100);
    } catch (Exception e){
      System.exit(0);
    }
    // display the new counter
    txtGenerated.setText("");
    txtGenerated.setText("" + counters[iThread]);
  }// increment

For 5 threads, the results are as follows:

0 completed
1 waiting
2 waiting
3 waiting
4 pending
0 completed
1 completed
1 completed
2 completed
2 completed
3 passed
3 completed
4 passed
4 completed

Let T0 through T4 be the five threads generated by the application. T0 is the first to acquire the sync lock and finds peutPasser set to true. It sets peutPasser to false and proceeds: this is the purpose of the first message sent. In all likelihood, it continues and executes the critical section, specifically the increment method. In this method, it will sleep for 100 ms (sleep). It therefore releases the processor. The processor is then granted to another thread, thread T1, which then acquires ownership of the sync object. It discovers that it cannot proceed and enters a wait state (wait). It then releases ownership of the synchro object as well as the processor. The processor is allocated to thread T2, which suffers the same fate. During the 100 ms that T0 is paused, threads T1 through T4 are therefore put on hold. This is the meaning of the 4 "waiting" messages. After 100 ms, T0 regains the processor and completes its work: this is the meaning of the "0 finished" message. It then releases one of the blocked threads and terminates. The freed processor is then assigned to an available thread: the one that has just been released. Here, it is T1. Thread T1 then enters the critical section: this is the meaning of the message "1 passed." It does what it needs to do and then pauses for 100 ms. The processor is then available for another thread, but all are waiting for an event: none of them can take the processor. After 100 ms, thread T1 reclaims the processor and terminates: this is the meaning of the message "1 finished." Threads T1 through T4 will behave the same way as T1: this is the meaning of the three series of messages: "passed," "finished."