1. Concurrent Programming with Threads

Programs often utilize threads as a programming aid, and operating systems provide them as a feature. If you are using Windows and check the task manager, you’ll see that there are roughly 3000 running threads. In this chapter, we aim to increase this number further to execute our own threads. However, the exercises should not solely focus on creating threads; concurrency requires careful attention to ensure proper access to shared resources, necessitating coordination among the threads.

Prerequisites

  • be able to use main thread

  • be able to create own threads, know the difference between Thread and Runnable

  • be able to put threads to sleep

  • be able to interrupt threads

  • be able to submit tasks to the thread pool for processing

  • be able to use concurrent operations with returns

  • understand the difference between Runnable and Callable

  • synchronize threads with Lock objects

  • be able to notify other threads with Condition objects

  • know synchronization helpers like Semaphore

Data types used in this chapter:

1.1. Create threads

When the JVM starts, it creates a thread named main. This thread executes the main(…​) method, and it has executed our programs in all previous exercises. We want to change that in the next exercises. We want to create more threads and let them execute program code.

Thread UML
Figure 1. UML diagram of the class 'thread

1.1.1. Create threads for waving and flag waving ⭐

There is a parade in honor of Captain CiaoCiao. He stands at the ramp of his ship, waves with one hand, and waves a flag with the other.

Exercise:

  • Threads always execute code of type Runnable in Java. Runnable is a functional interface, and there are two ways to implement functional interfaces in Java: classes and lambda expressions. Write two implementations of Runnable; one using a class, one using a lambda expression.

  • Put a loop with fifty repetitions in both Runnable implementations. One Runnable should output "wink" on the screen, the output of the other one should be "wave flag".

  • Create a Thread object, and pass the Runnable. Then start the threads. Do not start fifty threads, but only two!

Extension: The run() method of each thread should contain the statement System.out.println(Thread.currentThread());. What will be displayed?

Suppose Captain CiaoCiao has a few more arms to wave. How many threads can be created until the system comes to a standstill? Observe the memory usage in the Windows Task Manager (kbd: [Ctrl+Alt+Del]). Can you estimate how much a thread "costs"?

1.1.2. No more waving flags: End threads ⭐

Threads can be killed with stop() — the method has been deprecated for decades, but will probably never be deleted — or kindly asked to terminate itself with interrupt(). But for this, the thread has to play along and check with isInterrupted() whether such a termination request exists.

Captain CiaoCiao is still standing on the ship, winking and waving flags. When things get serious, he must stop this amusement for the people.

Exercise:

  • Write a program with two Runnable implementations that in principle wink and wave flags indefinitely, unless there is an interruption. The run() method should therefore use Thread.currentThread().isInterrupted() to test whether there was an interruption, and then exit the loop.

  • Build a delay into the loop. Copy the following code:

    try { Thread.sleep( 2000 ); } catch ( InterruptedException e ) { Thread.currentThread().interrupt(); }
  • The main program should respond to input with JOptionPane.showInputDialog(String) so that the commands endw stop winking and endf stop flag-waving.

1.1.3. Parameterize Runnable ⭐⭐

A glance at the following code shows that the two Runnable implementations are very similar, differing only in the screen output:

// Runnable 1
class Wink implements Runnable {
  @Override public void run() {
    for ( int i = 0; i < 50; i++ )
      System.out.printf( "Wink; %s%n", Thread.currentThread() );
      //                  ^^^^
  }
}
Runnable winker = new Wink();

// Runnable 2
Runnable flagWaver = () -> {
  for ( int i = 0; i < 50; i++ )
    System.out.printf( "Wave flag; %s%n", Thread.currentThread() );
    //                  ^^^^^^^^^
};

Code duplication is rarely good, though, this should be changed.

Exercise:

  • Figure out how to pass data to a Runnable.

  • Implement a parameterized Runnable, so that in the above loop one can

    • the screen outputs and

    • the number of repetitions.

  • Rewrite the wink-and-wave program so that the parameterized Runnable is passed to a thread for execution.

1.2. Execute and idle

A thread can be in several states, these include running, waiting, sleeping, blocked, or terminated. In the previous exercises, we started the thread so that it is in the running state, and we ended the run() method by completing the loop, which also ends the thread. In this section, the exercises are about the sleeping state, and in the section "Protecting Critical Sections", and "Thread Cooperation and Synchronization Helpers", there are exercises about the waiting/blocked states.

1.2.1. Delay execution by sleeping threads. ⭐⭐

The sleep program (http://man7.org/linux/man-pages/man1/sleep.1.html), known on Unix, can be invoked from the command line and then sleeps for a while, thus delaying subsequent programs in scripts.

Exercise:

  • Re-implement the sleep program in Java so that one can write comparable to the example on the command line:

    $ *java Sleep 22

    The Java program should then sleep for 22 seconds and if there are subsequent program invocations in a script, for example, they will be delayed.

  • The Java program should be able to be given the sleep time in various formats on the command line. If only an integer is passed, then the waiting time in seconds. Suffixes behind the integer should be allowed for different durations:

    • s for seconds (default)

    • m for minutes

    • h for hours

    • d for days

    If more than one value is passed, they are summed up to give the total waiting time.

  • Various things can go wrong with the call, for example, if no number is passed or the number is too large. Check if the values, ranges, and suffixes are correct. Optional: In case of an error, exit the program with an individual exit code via System.exit(int).

Example:

  • Valid call examples:

    $ java Sleep 1m
    $ java Sleep 1m 2s
    $ java Sleep 1h 3h 999999s
  • Invalid calls leading to termination:

    $ java Sleep
    $ java Sleep three
    $ java Sleep 1y
    $ java Sleep 999999999999999999

Tip: Structure the program so that the three essential parts are recognizable:

  1. running the command line and analyzing the arguments

  2. converting the units to seconds

  3. the actual sleeping, for the accumulated seconds

1.2.2. Watch file changes by threads ⭐

After successful looting, all new treasures are systematically added to an inventory list. This is saved in a simple file. Bonny Brain wants to be notified when the file changes.

Exercise:

  1. Write a class FileChangeWatcher with a constructor FileChangeWatcher(String filename).

  2. Implement Runnable.

  3. Output the filename, file size and Files.getLastModifiedTime(path) at half second intervals.

  4. Check with getLastModifiedTime(…​) if the file has changed. Print a message if the file changes. All this should happen endlessly, every new change should be reported.

  5. Extension: We now want to react more flexibly to changes. Therefore, we want to be able to pass a java.util.function.Consumer object in the constructor. The consumer should be stored by FileChangeWatcher and always call the accept(Path) method when something changes. This way, we can register an object that will be informed when a file change occurs.

1.2.3. Catch Exceptions ⭐

The distinction between checked exceptions and unchecked exceptions is important because if unchecked exceptions are not caught, this can escalate to the point where they end up at the executing thread, which is then terminated by the virtual machine. This is done automatically by the runtime environment, and we kindly get a message on the standard error channel, but we can’t revive the thread anymore.

On a local thread or globally for all threads, an UncaughtExceptionHandler can be installed, which is informed when an exception terminates the thread. It can be used in four scenarios:

  1. An UncaughtExceptionHandler can be set on an individual thread. Whenever this thread gets an unhandled exception, the thread is terminated and the set UncaughtExceptionHandler is informed.

  2. An UncaughtExceptionHandler can be set on a thread group.

  3. An UncaughtExceptionHandler can be set globally for all threads.

  4. The main thread is special in the sense that the JVM automatically creates it and executes the main program. Of course, there can be unchecked exceptions in the main thread as well, which can be reported by an UncaughtExceptionHandler. However, there is an interesting feature to this: At the main(…​) method there can be throws, and checked exceptions can thus go back to the JVM. In case of a checked exception, a set UncaughtExceptionHandler is also notified.

The processing takes place in a cascade: If there is an unchecked exception, the JVM first looks to see if an UncaughtExceptionHandler is set on the individual thread. If not, it looks for an UncaughtExceptionHandler in the thread group and then looks for a global handler to inform.

Exercise:

  • Run a thread that is terminated by division by 0. Use your own global UncaughtExceptionHandler to document this exception.

  • Start a second thread that has a local UncaughtExceptionHandler that ignores the exception, so no message appears either.

  • If the main(…​) method says throws Exception and the body says new URL("captain"), is the global UncaughtExceptionHandler also called?

1.3. Thread pools and results

It is not always the best way for Java developers to create threads themselves and associate these with program code; it is often more sensible to separate the program code from the physical execution. This is done in Java by an Executor. This makes it possible to separate the program code from the current thread, and also to use the same thread several times for different program codes.

In the Java library, there are three central Executor implementations: ThreadPoolExecutor, ScheduledThreadPoolExecutor, and ForkJoinPool. The ThreadPoolExecutor and ForkJoinPool types implement thread pools that manage a collection of existing threads so that exercises can be passed to existing free threads.

Each code execution in the background is realized by a thread in Java, which is either created and started by yourself or indirectly used by an Executor or internal thread pool. There are two important interfaces to encapsulate concurrent code: Runnable and Callable. Runnable is passed directly to the Thread constructor, a Callable cannot be passed to Thread; for Callable you need an Executor. A Callable also returns a result, as does a Supplier, but it has no parameter to pass. With a Runnable nothing can be returned and also not passed. The run() method does not throw an exception, call() has throws Exception in the method signature, so it can pass on any exceptions.

UML diagram of the Runnable and Callable interfaces

Runnable Callable UML

So far, we have always built threads ourselves and used only Runnable. The following exercises will be about thread pools and also about Callable.

1.3.1. Using thread pools ⭐⭐

Easter is coming up, and Bonny Brain goes to an orphanage dressed as a Wookiee with her crew members to deliver gifts.

Exercise:

  • Create an ExecutorService with Executors.newCachedThreadPool(), which is the thread pool.

  • Create a string array with presents.

  • Bonny Brain processes each gift in the main thread and passes it to a crew member, which is a thread in the thread pool, at 1 to 2 second intervals.

  • The crew members are threads in the thread pool. They execute Bonny Brain’s commands to distribute a gift. It takes them between 1 and 4 seconds to do this.

  • The flow is as follows: The gift distribution is implemented by a Runnable, the actual action. A free thread from the thread pool (the crew member) is selected and executes the Runnable. The Runnable needs a way to receive the gift from Bonny Brain.

1.3.2. Get last modification of web pages ⭐⭐

The following class implements a method that returns a timestamp in which a web page was last modified (the data may not be available, in which case the time is set to 1/1/1970). The server should send in zone time UTC ± 0.

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class WebChecker {

    public static void main(String[] args) throws IOException {
     ZonedDateTime urlLastModified = getLastModified(new URL("http://www.tutego.de/index.html"));
     System.out.println(urlLastModified);
     ZonedDateTime urlLastModified2 = getLastModified(new URL("https://en.wikipedia.org/wiki/Main_Page"));
     System.out.println(urlLastModified2);
    }

    private static ZonedDateTime getLastModified(URL url) {
     try {
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        long dateTime = con.getLastModified();
        con.disconnect();
        return ZonedDateTime.ofInstant( Instant.ofEpochMilli( dateTime ), ZoneId.of( "UTC" ) );
     } catch ( IOException e ) {
         throw new IllegalStateException(e);
     }
  }
}

Exercise:

  • Create a record WebResourceLastModifiedCallable with a record component URL url.

  • Let WebResourceLastModifiedCallable implement the Callable<ZonedDateTime> interface. Put the implementation of getLastModified(URL) from the example into the call() method. Does call() need to catch the checked exception itself?

  • Build WebResourceLastModifiedCallable objects, and let the thread pool execute them.

    • Let the Callable execute once with no time limit.

    • Give the Callable only one microsecond to execute; what is the result?

  • Optional: Convert the time since how many minutes relative to the current time the web page has changed.

1.4. Protect critical sections

When multiple parts of a program run simultaneously, they may access shared resources or memory areas. To avoid faulty states, these accesses must be synchronized, allowing one thread to finish its work before another thread accesses the same resource. Failing to coordinate access to shared resources can lead to errors.

Programs must protect critical sections, so that only one other thread may be in a section. Java provides two mechanisms :

  1. the keyword `synchronized

  2. Lock objects

synchronized is a convenient keyword, but limited in capability. The Java Concurrency Utilities provide more powerful data types. For "locking" exclusively executed program parts, the interface java.util.concurrent.locks.Lock and various implementations exist, such as ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock.

Lock ReentrantLock UML
Figure 2. UML diagram of the Lock and ReentrantLock types.

1.4.1. Writing memories into a poetry album ⭐

Once a cargo ship carrying valuable nepenthe has changed hands successfully, the pirates are chronicling their memories in a poetry album. The Captain CiaoCiao later adorns the album with stickers.

Drawing hearts

Given is the following program code in the main(…​) method of a class:

class FriendshipBook {
  private final StringBuilder text = new StringBuilder();

  public void appendChar( char character ) {
    text.append( character );
  }

  public void appendDivider() {
    text.append(
        "\n_,.-'~'-.,__,.-'~'-.,__,.-'~'-.,__,.-'~'-.,__,.-'~'-.,_\n" );
  }

  @Override public String toString() {
    return text.toString();
  }
}

class Autor implements Runnable {
  private final String text;
  private final FriendshipBook book;

  public Autor( String text, FriendshipBook book ) {
    this.text = text;
    this.book = book;
  }

  @Override public void run() {
    for ( int i = 0; i < text.length(); i++ ) {
      book.appendChar( text.charAt( i ) );
      try { Thread.sleep( 1 ); }
      catch ( InterruptedException e ) { /* Ignore */ }
    }
    book.appendDivider();
  }
}

FriendshipBook book = new FriendshipBook();

String q1 = "The flowers need sunshine and " +
            "I need Captain CiaoCiao to be happy";
new Thread( new Author( q1, book ) ).start();

String q2 = "When you laugh, they all laugh. " +
            "When you cry, you cry alone.";
new Thread( new Author( q2, book ) ).start();

TimeUnit.SECONDS.sleep( 1 );

System.out.println( book );

Exercise:

  • Before running the program, figure out the expected result.

  • Put the code in its own class and main(…​) method, and check the assumption.

  • The flaw in the code is the unrestrained access to the FriendshipBook. Improve the program with a Lock object so that the FriendshipBook can only be written to by one pirate at a time.

1.5. Thread cooperation and synchronization helper

It is important to synchronize program code so that two threads do not overwrite each other’s data. We’ve learned that this can be done with Lock objects. However, Lock objects only lock a critical area, and the runtime environment will automatically make a thread wait when a critical area is locked. An enhancement of this is to have a thread — or multiple threads — not only wait for entry into a critical section, but be informed via signals that it has something to do. Java provides different synchronization helpers with the internal states that cause other threads to wait or start executing when certain conditions are met.

  • Semaphore: Whereas a lock only allows a single thread to be in a critical section, a Semaphore allows a user-defined number of threads to be in a block. The method names are also slightly different: Lock declares the lock() method and Semaphore declares the acquire() method. If acquire() reaches the maximum number, a thread must wait for access, just as with a lock. A semaphore with a maximum count of 1 is like a Lock.

  • Condition: With a Condition a thread can wait and be woken up again by another thread. Using Condition objects, consumer-producer relationships can be programmed, but in practice, there is little need for this data type because there are Java types based on it, which are often simpler and more flexible. Condition is an interface, and the Lock object provides factory methods that return Condition instances.

  • CountDownLatch: Objects of type CountDownLatch are initialized with an integer, and various threads count down this CountDownLatch, which puts them in a waiting state. Finally, when the CountDownLatch reaches 0, all threads are released. A CountDownLatch is a way to bring different threads together at a common point. Once a CountDownLatch is consumed, it cannot be reset.

  • CyclicBarrier: The class is an implementation of a so-called barrier. With a barrier, several threads can meet at one point. If, e.g., work orders are parallelized and have to be rejoined later, this can be realized by a barrier. After all, threads have met this barrier, they continue to run. The constructor of CyclicBarrier can be passed a Runnable, which is called at the time of the coincidence. Unlike a CountDownLatch, a CyclicBarrier can be reset and reused.

  • Exchanger: Producer-consumer relationships occur frequently in programs, and the producer transmits data to the consumer. Exactly for this case, there is the class Exchanger. This allows two threads to meet at a point and exchange data.

1.5.1. Attending the Banquet with the Captains — Semaphore ⭐⭐

Bonny Brain and Captain CiaoCiao are planning a banquet with many companions. They both sit at a table with 6 seats and receive different guests. The guests come, stay a little, tell and eat, and leave the table again.

Attending Banquet with Captains

Exercise:

  • Create a Semaphore with as many seats as there can be guests at the table with the captains at the same time.

  • Model a guest as record Guest, which implements Runnable. All guests have a name.

  • Guests are waiting for a seat. It does not have to be fair, so the guest who has been waiting for the longest is not necessarily next to the table.

  • The program should do a screen output for a guest who would like to come to the table, for a guest who has been seated, and for a guest who is leaving the table.

1.5.2. Swearing and insulting — Condition ⭐⭐

Pirates don’t duel with cutlasses these days, they duel with curses.

Exercise:

  • Start two threads, each representing two pirates; give each thread a name.

  • A random pirate starts cursing and gets an endless insult contest going.

  • The curses should be random from a given collection of curses.

  • Before the actual curse, a pirate may take a "pause for thought" of up to one second.

1.5.3. Take pens out of paintbox — Condition ⭐⭐

In kindergarten, the little pirates regularly get together and paint pictures. Unfortunately, there is only one box with 12 pencils. When one child has taken pens from the box, another child has to wait whenever he wants more pens than are available in the box.

The scenario can be well implemented with threads.

Exercise:

  • Create a class Paintbox. This class should get a constructor and accept the maximum number of free pens.

  • In the class Paintbox place a method acquirePens(int numberOfPens), by which the children can request several pens. The requested number of pens may be greater than the available number, in which case the method shall block until the number of requested pens is available again.

  • The class Paintbox additionally has the method releasePens(int numberOfPens) for putting the pens back. This method signals that pens are available again.

  • Create a class Child.

  • Give the class Child a constructor so that each child has a name and can get a reference to a paintbox.

  • The class Child shall implement the interface Runnable. The method shall determine a random number between 1 and 10, representing the number of pencils desired. Then the child requests this number of pens from the paintbox. The child uses the pencils for between 1 and 3 seconds and then puts all the pencils back into the paintbox — no more and no less. Subsequently, the child waits between 1 and 5 seconds and starts again, claiming a random number of pencils.

Painting can be started with the following children:

public static void main( String[] args ) {
  Paintbox paintbox = new Paintbox( 12 );
  ExecutorService executor = Executors.newCachedThreadPool();
  executor.submit( new Child( "Mirjam", paintbox ) );
  executor.submit( new Child( "Susanne", paintbox ) );
  executor.submit( new Child( "Serena", paintbox ) );
  executor.submit( new Child( "Elm", paintbox ) );
}

1.5.4. Play Rock, Paper, Scissors — CyclicBarrier ⭐⭐⭐

Rock, Paper, Scissors is an old game that was played as early as the 17th century. After a start signal, the two players form a shape for scissors, stone, or paper with one hand. Which player wins is determined by the following rules:

  • Scissors cuts the paper (scissors wins).

  • Paper wraps the stone (paper wins).

  • Rock blunts the scissors (rock wins).

So, each hand sign can win or lose.

We want to write a simulation for the game and take the following enumeration for hand signs as a base:

[[source,java]

enum HandSign {
  SCISSORS, ROCK, PAPER;

  static HandSign random() {
    return values()[ ThreadLocalRandom.current().nextInt( 3 ) ];
  }

  int beats( HandSign other ) {
    return (this == other) ? 0 :
           (this == HandSign.ROCK && other == HandSign.SCISSORS
            || this == HandSign.PAPER && other == HandSign.ROCK
            || this == HandSign.SCISSORS && other == HandSign.PAPER) ? +1 : -1;
 }
}

The enum HandSign declares three enumeration elements for scissors, stone, paper. The static method random() returns a random hand sign. The beats(HandSign) method is similar to a comparator method: it compares the current hand sign with the passed hand sign and returns 0 if both hand signs are equal, +1 if the own hand sign is higher than the passed sign, and -1 otherwise.

Play Rock Paper Scissors CyclicBarrier

Exercise:

  • Run a starter thread that triggers a snick-snack game every second. For repeated execution, a ScheduledExecutorService can be used.

  • A player is represented by a Runnable that chooses a random hand signal and puts the choice into a data structure of type ArrayBlockingQueue with add(…​).

  • After a player picks a hand sign, the await() method is to be called on a previously constructed CyclicBarrier.

  • The constructor of CyclicBarrier shall get a Runnable which determines the winner at the end of the game. The Runnable takes out of the ArrayBlockingQueue the two hand signs with poll(), compares them, and evaluates the winner and loser. At the first position of the data structure is player 1, at the second position player 2.

1.5.5. Find the fastest runner — CountDownLatch ⭐⭐

For the next heist, Bonny Brain needs fast runners. For this, she hosts a competition and lets the best runners compete. With a starting gun, Bonny Brain stands at the track, and everyone waits for the starting gun.

Exercise:

  • Create 10 threads that wait for Bonny Brain’s signal. Thereafter, the threads start and take between a freely chosen number of 10 and 20 seconds to run. In the end, the threads should write their time into a common data structure, so that the name of the thread (runner name) is noted with the runtime.

  • Bonny Brain starts the runners in the main thread and at the end outputs all run times sorted in ascending order with the runner names.

If several threads are to come together in one place, a CountDownLatch can be used well for this. The CountDownLatch is initialized with an integer (a counter) and provides two central methods:

  • countDown() decrements the counter,

  • await() blocks until the counter becomes 0.