Threads

The Java language supports concurrency by providing an API to create and manipulate OS Threads: the Thread class.

References:

Accessing the current thread

  • Save the following code in a file named Concurrency.java (the file should have the same name as the class it contains)
  • Execute the file, which can be:
    • on the command line: java Concurrency.java
    • in your favorite IDE (unless your favorite IDE isn't IntelliJ IDEA, in which case we strongly suggest using IntelliJ IDEA)
public class Concurrency {

    public static void log(Object o) {
        Thread thread = Thread.currentThread();
        System.out.println("[" + thread.getName() + "] " + o);
    }

    public static void main(String[] args) {
        log("hello");
    }
}
  • What does the log function do?
  • What is the name of the thread in which the main function is executing?

The Thread.currentThread() method lends you a description of the OS Thread on which the calling function is executing, in the form of an instance of Thread.

By default, there is a unique thread, used to execute your main function Note: that the JVM process may launch additional threads for, e.g., garbage collection or JIT compilation.

  • What is the priority of the default thread? (use the javadoc linked at the top)

Creating Java threads

While the JVM creates a main thread to run you program, you have the possibility of creating and starting your own thread.

For this you should:

  1. create a new Thread instance and provide it with the code to run on the thread (we will see two methods for this)
  2. call the start() method of the Thread instance.

Method 1: Extending Thread

The first way to specify the code to run by extending the Thread class and overriding the run()method.

⚠ Never execute the run() method: it will NOT start a new thread.

private static class GreeterThread extends Thread {
    @Override
    public void run() {
        log("Greetings !!!");
    }
}
public static void main(String[] args) {
    Thread t = new GreeterThread();
    t.start();  // NOT run() !
}
  • Run the above code. What's the name of the Thread on which the greetings are made?
  • If you replace start() with run(), what is the difference ? (make sure to UNDO this change!)
  • Before starting it, set the name of the thread to something more meaningful.
  • Update the GreeterThread so that you can give it the name of the person to greet as an attribute of the class.

Method 2: Providing a Runnable to the Thread

The other possibility to provide executable code to a thread is to give it a Runnable object. The Runnable interface lets you implement a single run() method that specify code to be executed later.

When a Runnable instance is passed in the parameters of a Thread's constructor, the runnable is stored inside the thread instance and will be executed by the start() method:

private static class GreeterRunnable implements Runnable {
    @Override
    public void run() {
        log("Hi from runnable");
    }
}

public static void main(String[] args) {
    Thread t = new Thread(new GreeterRunnable());
    t.start();
}

Alternative syntax: Instead of explicitly declaring a new GreeterRunnable class, you can instead use an inline class that implicitly creates a subclass of Runnable:

Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        log("Hi from runnable");
    }
});
t.start();

Alternative syntax: since the Runnable a Single Abstract Method (SAM class), you can use the lambda notation to define it. The code below is equivalent to the one above. (notes on functional interface)

Thread t = new Thread(() -> log("Hi from runnable"));
t.start();

Exercise: Write a function that takes as input a list of Strings and for each name in the list greets it from a distinct thread (you should start as many threads as they are names).

private static void greetAll(List<String> names) {
   ...
}

Exercise: By joining threads, make sure that all persons are greeted in the original order. (You will need to look into the javadoc of Thread)

Pausing Threads (sleep)

The Thread class proposes a static method to pause the current thred for given amount of time: Thread.sleep().

Exercise: Use this to create a ticker thread that will indefinetely print tick on the standard output every second.

Stopping Threads

With your ticker, it should be the case that your program never terminates, which is probably not what is intended.

The rule is:

  1. a thread stops when its run() method terminates
  2. the JVM will keep running until all non-daemon threads are terminated.

Exercise: Configure your ticker to be a daemon thread and check that it does not prevent the JVM from exiting anymore.

Now turn back your ticker to non-daemon, we are going to stop it.

Exercise: Modify your ticker class so that is has a boolean field saying whether it should stop

⚠ Like ALL datastructures that are accessed my multiple threads, you should take defensive measures to prevent data races. (Here, an AtomicBoolean might be found useful).

While doing the previous exercises, you may have found out that some operations (join and sleep) may throw and InterruptedException when interrupted.

Exercise: Use the Thread.interrupt() method to gracefully halt the Thread without needing a boolean flag. By gracefully, we mean that your program should not crash down in flames while filling the standard output with exceptions. Instead your ticker thread should die of peaceful natural death (i.e. by reaching the end of its run() method).

Scheduling Tasks

References:

Exercise: Using the Timer class, reimplement your ticker to run every second and count the elapsed seconds.

  • Compared to your previous implementation with a sleep, what are the additional guarantees that you get from the Timer API?
  • Can you schedule multiple tasks with the same timer ?
  • Is the thread on which the timer is running a daemon thread ?

Synchronization

Preliminary Exercise: Using normal threads create a situation whee you can witness a race condition. While there are many options, the easiest is probably to increment a static int from multiple threads.

Read the following tutorial on the synchronized keyword: tutorial synchronized

  • Use the synchronized keyword to remove the data race of your int counter.

Exercise: Use the synchronized keyword to implement a ConcurrentQueue: a thread safe implementation of a FIFO queue where there might be several producer threads and several consumer threads working concurrently.

To keep things simple in the beginning:

  • make your queue only hold Object values
  • throw an exception if the queue is empty on a pop() operation.

Test your queue on a small consumer/producer example. For instance, you can have several threads producing random numbers and a consumer that averages the numbers.

Going further:

  • make your queue implementation wait if there is a pop() on the empty queue (you may need a java.util.concurrent.Semaphore for this)
  • Give you queue a maximum capacity and make the producer wait if there is no space on push().
  • make your queue generic in the type of the element
  • make your queue implements the Queue<T> interface of the standard library.