Java Tutorials Made Easy banner

  

Section 1 - The Need for Multithreading

 

The program in Listing 1 prints a number, then prompts the user if he or she wants to print another number. As shown below, the program prints the number 5 with each iteration of the while loop.

 

Listing 1 - PrintANumber.java

import java.util.*;

public class PrintANumber
{
    private static int number = 5;

    public static void main(String[] args)
    {
        Scanner scanner = new Scanner(System.in);
        String response = "Y";

        while (response.charAt(0) == 'Y')
        {
            System.out.println("The number is " + number);

            System.out.print("Do you want to print another number"
                           + "('Y' to continue)? ");

            response = scanner.nextLine();
        }
    }
}

OUTPUT (User Input shown in RED):

The number is 5

Do you want to print another number('Y' to continue)? Y

The number is 5

Do you want to print another number('Y' to continue)? Y

The number is 5

Do you want to print another number('Y' to continue)? N

 

To print a different number with each iteration, we would normally change the value of the number inside the while loop. To print different numbers without modifying the while loop, the following statements can be added to the beginning of the main method:

        Runnable r = () -> { 

            while(true) 
                number = new Random().nextInt(100);
        };

        Thread t = new Thread(r);

        t.setDaemon(true);
        t.start();

A simple Java program runs as a single thread of execution which is passed control by the JVM.

The thread begins execution at the first statement in the main method and terminates when the main method has completed.

 

A single Java program may consist of many threads of execution which run concurrently. The code listed above creates a second thread of execution which computes random integers inside a loop. This thread runs concurrently with and asynchronously from the main method. 

 

The complete program is shown in Listing 2. The main method prints the value of the number most recently set by the second thread, then waits for the user to select whether or not to continue. In the meantime, the second thread computes many new numbers, but only the most recent value calculated before the user responds with ‘Y’ at the prompt is printed. 

 

Listing 2 - PrintANumber.java (version 2)

import java.util.*;

public class PrintANumber 
{
    private static int number = 5;

    public static void main(String[] args)
    {
        Runnable r = () -> { 

            while(true) 
                number = new Random().nextInt(100);
        };

        Thread t = new Thread(r);

        t.setDaemon(true);
        t.start();

        Scanner scanner = new Scanner(System.in);
        String response = "Y";

        while (response.charAt(0) == 'Y')
        {

            System.out.println("The number is " + number);

            System.out.print("Do you want to print another number"
                           + "('Y' to continue)? ");

            response = scanner.nextLine();
        }
    }
}

OUTPUT (User Input shown in RED):

The number is 11

Do you want to guess another number('Y' to continue)? Y

The number is 10

Do you want to guess another number('Y' to continue)? Y

The number is 18

Do you want to guess another number('Y' to continue)? N

 

Section 2 - The Runnable Interface

 

Runnable is a functional interface with functional method run that takes no arguments and returns void

public interface Runnable 
{
    void run();
}

Runnable objects are frequently used to define the tasks which run in execution threads. The program in Listing 2 contains a Runnable object that defines a task that computes random numbers in a loop. This task runs on the second execution thread of the program. 

 

Any named or anonymous class that implements the Runnable interface must provide an implementation for the run method.

class MyRunnable implements Runnable 
{
    public void run() 
    {
        System.out.println("Pluto");
    }
}

MyRunnable mine = new MyRunnable();

mine.run(); // prints "Pluto"

These are seldom used, however, since the Runnable interface is more easily implemented using a lambda expression.

Runnable r = () -> System.out.println("Pluto");

r.run(); // prints "Pluto"

If the run method of a Runnable object is called directly, it executes in the current execution threads, as in the example above.

 

Section 3 - The Callable Interface

 

If the task which executes a thread needs to return a value, then Callable should be used instead of RunnableCallable is a functional interface with functional method call that takes no arguments and returns generic type V

public interface Callable<V> 
{
    V call();
}

Since the call method may throw a checked exception, it should be placed inside a try/catch statement. In the following example, the Callable object defines a task that return the string “Neptune”. Since the call method is invoked directly it runs in the current execution thread.

        Callable<String> c = () -> "Neptune";

        try {
            String s = c.call();

            System.out.println(s); // prints "Neptune"

        } catch (Exception ex) {}

 

Section 4 - The Thread Class

 

The Thread class creates a separate execution thread on which to run a task, and provides 

control on how that task is executed. If a Runnable object is passed as an argument to the Thread class constructor, the task defined in the Runnable object runs when the thread runs. The following example assigns the task in Runnable r to thread T. Note that this merely attaches the Runnable object to the Thread object, and does not execute the task.

Runnable r = () -> System.out.println("Uranus");

Thread t = new Thread(r); // Nothing happens yet

The start method of the Thread class needs to be called to start the new execution thread and execute the Runnable.

t.start(); // prints "Uranus"

If the Thread object is not accessed the thread is started, a reference variable does not need to be created.

(new Thread(r)).start(); // prints "Uranus"

The Runnable and Thread creation can be combined into one statement.

(new Thread( 

    () -> System.out.println("Saturn"))

   ).start(); // prints "Saturn"

 

Section 5 - Daemon Threads

 

A Java program does not terminate until all its threads are complete. In the program in Listing 3, the task running in thread t runs infinitely even though the main method has returned. If the main thread completes and another thread is still running, the program does not terminate. 

 

Listing 3 - TestDaemon.java 

public class TestDaemon 
{
    public static void main(String[] args)
    {
        Runnable r = () -> { while(true); };

        Thread t = new Thread(r);

        t.start();

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

OUTPUT:

End of main method

(program does not complete)

 

daemon thread runs a background task. When all the non-daemon threads have completed, the JVM will shut down any daemon threads that are running, and terminate the program. Thread in the previous example is marked as a daemon thread by calling its the thread class’s setDaemon(boolean) method with its argument set to true. Therefore, when the main terminates, the JVM shuts down thread t, and the program terminates.

 

Listing 4 - TestDaemon.java (version 2)

public class TestDaemon 
{
    public static void main(String[] args)
    {
        Runnable r = () -> { while(true); };

        Thread t = new Thread(r);

        t.setDaemon(true);
        t.start();

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

OUTPUT: 

End of main method

(program terminates normally)

 

Section 6 - Extending the Thread class

 

The previous examples created a Thread object by passing a Runnable object to the Thread class’s constructor. Since the Thread class implements the Runnable interface, it can be extended. The subclass can then override the run method to define the task that runs in the thread. 

 

The following class extends the Thread class and defines a constructor that initializes field x. The run method is then overridden to print out the value of x.

The following statement creates an instance of MyThread and initializes field x to 1. The run method prints “x = 1” when the object’s thread executes. 

    (new MyThread(1)).start(); // Prints "x = 1"

 

Section 7 - Putting a Thread to Sleep

 

Any thread can be put to sleep by calling the thread class’s static sleep(long millis) method. Since this method may throw an InterruptedException, it must be placed in a try/catch block.

 

To put a thread to sleep for one second, use the following code:

try {

    thread.sleep(1000);  // 1000 milliseconds = 1 second

} catch (InterruptedException ex) {}

The program in Listing 5 contains the main thread and a secondary thread. Both threads contain loops inside which they sleep for one second, print that they are awake and their current execution count. The main thread initially sleep for .5 seconds to put the threads out of phase so that the output is predictable.

 

Listing 5 - TestThreadSleep.java

class Sleeper extends Thread
{
    @Override public void run()
    {
        int rcount = 0;

        while(rcount < 5)
        {
            try {
                Thread.sleep(1000);

                rcount++;
                System.out.println("Runnable awake, count = " 
                                 + rcount);
            } catch (InterruptedException ex) {}
        }
    }
}
 
public class TestThreadSleep 
{
    public static void main(String[] args)
    {
        (new Sleeper()).start();

        int mcount = 0;

        try {
            Thread.sleep(500);

            mcount++;
            System.out.println("Main awake, count = " + mcount);

        } catch (InterruptedException ex) {}

        while(mcount < 5)
        {
            try {
                Thread.sleep(1000);

                mcount++;
                System.out.println("Main awake, count = " + mcount);

            } catch (InterruptedException ex) {}
        }
    }
}

OUTPUT:

Main awake, count = 1

Runnable awake, count = 1

Main awake, count = 2

Runnable awake, count = 2

Main awake, count = 3

Runnable awake, count = 3

Main awake, count = 4

Runnable awake, count = 4

Main awake, count = 5

Runnable awake, count = 5

 

Section 8 - Atomic Operations

 

The program in Listing 2 is unsafe since the main thread could read the number field while the second thread is in the middle of a write operation. The data read back for the number field could be corrupted.

 

The Java API provides classes that provide atomic read and write operations. Atomic operations run to completion and cannot be interrupted by another thread. The program in Listing 6 replaces the int number field with an AtomicInteger object. The AtomicInteger class defines get and set methods that provide atomic read and write operations. Even though both threads are accessing field number asynchronously, the read and writeoperations are not corrupted since variable field is an atomic integer.

 

Listing 6 - PrintANumber.java (version 3)

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

public class PrintANumber
{
    private static AtomicInteger number = new AtomicInteger(5);

    public static void main(String[] args)
    {
        Runnable r = () -> { 

            while(true) 
                number.set(new Random().nextInt(100));
        };

        Thread t = new Thread(r);

        t.setDaemon(true);
        t.start();

        Scanner scanner = new Scanner(System.in);
        String response = "Y";

        while (response.charAt(0) == 'Y')
        {
            System.out.println("The number is " + number.get());

            System.out.print("Do you want to print another number"
                           + "('Y' to continue)? ");

            response = scanner.nextLine();
        }
    }
}

OUTPUT (User Input shown in RED):

The number is 52

Do you want to print another number('Y' to continue)? Y

The number is 49

Do you want to print another number('Y' to continue)? Y

The number is 23

Do you want to print another number('Y' to continue)? N

 

Section 9 - Synchronizing Threads

 

In the previous examples, threads are run asynchronously, meaning that the execution of a thread is independent of all other threads. Threads can be synchronized, meaning that one thread will wait until an event occurs on another thread before continuing. Two mechanisms for synchronizing threads are thread joins and semaphores. 

 

Section 9a - Joining Threads

 

If a thread is joined with another thread, it waits until the other thread completes before continuing. The Thread class provides a join method that will cause the calling thread to wait until the called thread completes. Since this method may throw an InterruptedException, it must be called inside a try/catch block. Since the join method call is performed after the start method call, a reference variable should be created for the Thread object.

 

The program in Listing 7 defines the JoinMe class whose run method goes to sleep for five seconds before completion. The main method creates a JoinMe object and starts its thread. The main method then calls the join method of the JoinMe object. Next, the main method waits five seconds until the thread running JoinMe.run completes. Then, the main method receives control, prints that it has finished, and terminates. 

 

Listing 7 - TestJoins.java

class JoinMe extends Thread
{
    @Override public void run()
    {
        System.out.println("JoinMe started");

        try {
            Thread.sleep(5000);

            System.out.println("JoinMe completed 5 second sleep");

        } catch (InterruptedException ex) {}

        System.out.println("JoinMe exits");
    }
}

public class TestJoins 
{
    public static void main(String[] args)
    {
        Thread j = new JoinMe();

        j.start();

        try {
            System.out.println(
               "main waits for JoinMe to complete...");

            j.join();

        } catch (InterruptedException ex) {}

        System.out.println("main exits");
    }
}

OUTPUT:

main waits for JoinMe to complete...

JoinMe started

JoinMe completed 5 second sleep

JoinMe exits

main exits

 

Section 9b - Interrupting Threads

 

The main thread in the program of Listing 7 waits for the JoinMe thread to complete before joining, and therefore wait for the five second sleep to complete. The main thread could have interrupted the JoinMe thread before the sleep had completed by calling the Thread class’s interrupt method. The main thread in the program of Listing 8 interrupts the five second sleep of the InterruptMe thread after only two seconds. Note that "InterruptMe completed 5 second sleep" is not printed. 

 

Listing 8 - TestInterrupt.java

class InterruptMe extends Thread
{
    public void run()
    {
        System.out.println("InterruptMe started");

        try {
            Thread.sleep(5000);

            System.out.println(
               "InterruptMe completed 5 second sleep");

        } catch (InterruptedException ex) { 

            System.out.println("InterruptMe interrupted");
        }

        System.out.println("InterruptMe exits");
    }
}

public class TestInterrupt 
{
    public static void main(String[] args)
    {
        Thread t = new InterruptMe();

        t.start();

        try {
            System.out.println(
              "main waits 2 seconds, then interrupts InterruptMe...");

            Thread.sleep(2000);

            t.interrupt();

            t.join();

        } catch (InterruptedException ex) {}

        System.out.println("main exits");
    }
}

OUTPUT:

main waits 2 seconds, then interrupts InterruptMe...

InterruptMe started

InterruptMe interrupted

InterruptMe exits

main exits

 

Section 9c - Semaphores

 

A semaphore is a resource that can be used to synchronized threads programmatically. The Java API provides a Semaphore class that contains a semaphore. The class constructor initializes the semaphore to a value. When the value of the semaphore reaches zero, the calling thread goes to sleep until the value becomes greater than zero. The release method increases the value of the semaphore by one so that a thread that is sleeping on the Semaphore object wakes up. The acquire method decreases the value of the semaphore by one, and if the value is zero the thread waiting on the Semaphore object goes to sleep.

 

Although the program in Listing 6 is free from data corruption, it is inefficient, since it computes the number field countless times per each iteration of the while loop in the main method. A semaphore can be used to synchronize the two threads so that the number field is computed once per each iteration of the while loop in the main method.

 

The program in Listing 9 uses a Semaphore object to synchronize the two threads. It follows the producer consumer design pattern. The producer performs an action or generates data that is needed by the consumer. The following diagram shows semaphore usage in a producer consumer ecosystem. The producer thread is started first, but needs to be put to sleep to allow the consumer thread to release the semaphore. The consumer thread releases the semaphore (indicating it is ready to receive new data), then goes to sleep to allow the consumer thread to generate the data. The producer thread then acquires the semaphore, generates the data and releases the semaphore. Finally the consumer thread acquires the semaphore and processes the data. The operations are typically repeated in a loop.

                                           

Figure 1 - Typical interaction of the producer and consumer

 

In the PrintANumber program (version 4), the producer is implemented by Runnable object r

which generates a random number after acquiring the semaphore. The consumer is

implemented in the main method which prints the number after acquiring the semaphore. The release and acquire methods of the Semaphore object need to be placed in try/catch blocks.

These operations are placed in while loops so that they can be completed. The main method reads sentinel variable response from a Scanner object in order to determine when the program should finish. When the response is “Y”, the main thread interrupts and the runnable thread (the consumer interrupts the producer), joins the runnable to wait for its completion, then returns to the JVM so the program can terminate.

 

Listing 9 - PrintANumber (version 4)

import java.util.*;

import java.util.concurrent.Semaphore;

public class PrintANumber
{
    private static int number = 5;
    private static Semaphore sema = new Semaphore(0);

    public static void main(String[] args)
    {
        Runnable r = () -> {
            boolean interrupted = false;

            while(!interrupted) 
            {
                try {

                    Thread.sleep(500);

                    sema.acquire();

                    number = new Random().nextInt(100);

                    System.out.println("Runnable computes number = " 
                                      + number);

                    sema.release();

                } catch (InterruptedException ex) { 

                    interrupted = true; 
                }
            }
        };

        Thread t = new Thread(r);
        t.start();

        Scanner scanner = new Scanner(System.in);
        String response = "Y";

        try {
            while (response.charAt(0) == 'Y')
            {
                System.out.println("main requests a number...");

                sema.release();

                Thread.sleep(500);

                sema.acquire();

                System.out.println("main receives number = " 
                                  + number);
                System.out.print("Do you want to print another number"
                           + "('Y' to continue)? ");

                response = scanner.nextLine();
            }

            t.interrupt();
            t.join();

        }   catch (InterruptedException ex) {}
    }
}

OUTPUT (User Input shown in RED):

main requests a number...

Runnable computes number = 91

main receives number = 91

Do you want to print another number('Y' to continue)? Y

main requests a number...

Runnable computes number = 54

main receives number = 54

Do you want to print another number('Y' to continue)? Y

main requests a number...

Runnable computes number = 76

main receives number = 76

Do you want to print another number('Y' to continue)? N


Section 10 - More Complex Producer Consumer Programs

 

Since producer consumer programs usually involve more processing, the producer and the consumer are each typically implemented as separate classes. If they are private inner classes, the producer and consumer can share the semaphore defined in the outer class. 

 

The program in Listing 10 uses the producer consumer design pattern to prompt the user for a string, then print the string. It defines inner classes Producer and Consumer that share a semaphore and manage it according to the logic given in Figure 1. Both classes also share String field data defined in the outer class. Both the Producer and Consumer classes extend the Thread class, and start their underlying thread inside the constructor. 

 

The ProducerConsumer class constructor initializes the shared semaphore to zero and initializes the string to an empty string. It then instantiates the producer followed by the consumer. It then joins both threads. When the consumer thread receives the string “QUIT”, it interrupts the producer thread, allowing the threads to be joined. The constructor then returns and the program terminates.

 

Listing 10 - ProducerConsumer.java

import java.util.concurrent.Semaphore;
import java.util.Scanner;

public class ProducerConsumer 
{
    private Semaphore sema;
    private String data;

    private Producer producer;
    private Consumer consumer;

    private class Producer extends Thread
    {
        private Scanner keyboard;

        Producer()
        {
            keyboard = new Scanner(System.in);

            this.start();
        }

        public void run()
        {
            boolean interrupted = false;

            while(!interrupted) 
            {
                try {
                    Thread.sleep(500);

                    sema.acquire();

                    System.out.print("Enter String:");
                    data = keyboard.nextLine();

                    sema.release();

                } catch (InterruptedException ex) { 

                    interrupted = true; 
                }
            }
        }
    } 

    private class Consumer extends Thread
    {
        Consumer()
        {
            this.start();
        }

        public void run()
        {
            while(!data.equals("QUIT"))
            {
                try {
                    sema.release();

                    Thread.sleep(500);

                    sema.acquire();

                    System.out.println("consumer has [" + data + "]");

                } catch(InterruptedException e) {}
            }
            producer.interrupt();
        }
    }
 
    public ProducerConsumer()
    {
         sema = new Semaphore(0);

         data = "";

         producer = new Producer();

         consumer = new Consumer();

         try {
             consumer.join();
             producer.join();
         }
         catch (InterruptedException e) {}
    } 

    public static void main(String[] args)
    {
        new ProducerConsumer();
    }        
}

OUTPUT (User Input shown in RED):

Enter String:Hello

consumer has [Hello]

Enter String:Goodbye

consumer has [Goodbye]

Enter String:QUIT

consumer has [QUIT]