1. Concurrent Programming with Threads

An operating system offers threads as a programming aid, which programs frequently use. If you work under Windows and take a look at the task manager, you will notice that there are easily 3000 threads running. In this chapter we want to increase the number once again to execute our own threads. The exercises should not simply be about creating threads, but if something is concurrent, attention must also be paid to correct access to shared resources — the threads must coordinate.

Prerequisites

  • be able to use main thread

  • be able to create own threads, know 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 statemtent 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 an 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 (differences in the run() method bold):

// 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 formates on the command line. If only an integer is passed, then the waiting time is 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 agruments

  2. converting the units to seconds

  3. the actual sleeping, for the accumulated seconds

1.2.2. Watch file changes by threads ⭐

After a 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 flexible on 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 the thread is terminated by an exception. 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 at 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 actual thread and also to use the same thread several times for different program code.

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 new 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.

*Java 8 Backport

Users of Java 8 put a class WebResourceLastModifiedCallable with a constructor instead of a record, to which we can pass a URL.

1.4. Protect critical sections

If several program parts run concurrently, they may access shared resources or memory areas. Such accesses must be synchronized so that one thread can finish work until another thread accesses that resource. If accesses to shared resources are not coordinated, faulty states will result.

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 ⭐

After a cargo ship with valuable nepenthe has successfully changed "owners", the pirates are writing their memories in a poetry album, which Captain CiaoCiao later decorates with stickers.

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 = "Die Blumen brauchen Sonnenschein " +
    "und ich brauch Capatain CiaoCiao zum Fröhlichsein";
new Thread( new Autor( q1, book ) ).start();

String q2 = "Wenn du lachst, lachen sie alle. " +
    "Wenn du weinst, weinst du alleine";
new Thread( new Autor( 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 have seen 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 internal state 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 at 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.

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 class 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 the longest is 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 the threads names.

  • 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. Thid 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 a number of 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 paint box. 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. After that, 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.

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 chooses 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. After that, the threads start and take between a freely chosen number of 10 and 20 seconds to run. At 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.

1.6. Suggested solutions

1.6.1. Create threads for waving and flag waving

The following is part of the main(…​) method:

com/tutego/exercise/thread/CaptainsParade.java
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 flagWaver = () -> {
  for ( int i = 0; i < 50; i++ )
    System.out.printf( "Wave flag; %s%n", Thread.currentThread() );
};

Thread winkerThread = new Thread( winker );
Thread flagWaverThread = new Thread( flagWaver, "flag waver" );

winkerThread.start();
flagWaverThread.start();

In the body of the run() method and in the lambda expression we find a simple loop with the desired outputs. After building the Runnable instances, they need to be connected to the thread. To do this, the Runnable objects are passed to the constructor of Thread. The constructor is overloaded several times; one variant allows setting a name, which we do for the flag wavers. Building the thread instances does not start a thread; this requires a call to the Thread method start().

Wink; Thread[Thread-0,5,main]
Wave flag; Thread[flag waver,5,main]
Wave flag; Thread[flag waver,5,main]
Wave flag; Thread[flag waver,5,main]
Wink; Thread[Thread-0,5,main]
Wink; Thread[Thread-0,5,main]
Wink; Thread[Thread-0,5,main]
Wink; Thread[Thread-0,5,main]
...

The output shows the toString() representation of Thread, which consists of the thread name, thread priority, and thread group. From the output, you can see that thread-0 (which is the automatically assigned name) and flag waver alternate. Each time they are called, the output will look slightly different — concurrent programs are typically non-deterministic.

1.6.2. No more waving flags: End threads

com/tutego/exercise/thread/CaptainsParadeIsInterrupted.java
Runnable winker = () -> {
  while ( ! Thread.currentThread().isInterrupted() ) {
    System.out.printf( "Wink; %s%n", Thread.currentThread() );
    try { TimeUnit.SECONDS.sleep( 2 ); }
    catch ( InterruptedException e ) { Thread.currentThread().interrupt(); }
  }
};

Runnable flagWaver = () -> {
  while ( ! Thread.currentThread().isInterrupted() ) {
    System.out.printf( "Wave flag; %s%n", Thread.currentThread() );
    try { TimeUnit.SECONDS.sleep( 2 ); }
    catch ( InterruptedException e ) { Thread.currentThread().interrupt(); }
  }
};

Thread winkerThread    = new Thread( winker );
Thread flagWaverThread = new Thread( flagWaver );

winkerThread.start();
flagWaverThread.start();

String message = "Submit 'endw' or 'endf' to end the threads or cancel to end main thread";
for ( String input;
      (input = JOptionPane.showInputDialog( message )) != null; ) {
  if ( input.equalsIgnoreCase( "endw" ) )
    winkerThread.interrupt();
  else if ( input.equalsIgnoreCase( "endf" ) )
    flagWaverThread.interrupt();
}

In order for a thread to respond to an interrupt, the interrupt flag must be deliberately polled in the Runnable. This query is handled by the isInterrupted() method. We put the query in a while loop, because we want to execute our operation as long as the flag is not yet set. In the body of both loops there is a console output and a wait of 2 seconds. There is a special feature to note when sleeping: If we sleep and then get interrupted, the sleep(…​) method first throws an InterruptedException, and second it resets the interrupt flag. So we have to set the interrupt flag again in the catch block and fall back to the interrupt() method. This is also exactly the method we use in the main(…​) method to signal a selected thread to terminate itself.

There is a significant difference between killing a thread with stop() and setting a flag: when calling the stop() method, the thread is killed hard, and it can be in any state. Setting an interrupt flag requires the active cooperation of the thread. The thread must independently request the flag and exits the run() method without exception.

1.6.3. Parameterize Runnable

The run() method has no return and no parameter list. Therefore, the run() method must take parameters in some other way. This can be done by using variables that the run() method can access.

Two suggested solutions:

com/tutego/exercise/thread/ParameterizedRunnable.java
class PrintingRunnable implements Runnable {
  private final String text;
  private final int repetitions;

  PrintingRunnable( String text, int repetitions ) {
    this.text = text;
    this.repetitions = repetitions;
  }

  @Override public void run() {
    for ( int i = 0; i < repetitions; i++ )
      System.out.printf( "%s; %s%n", text, Thread.currentThread() );
  }
}

If we write a new class that implements Runnable, we can give it a constructor that takes states. We can store these states in object variables. If we then call the constructor, the values are set when the Runnable object is built, and if the thread later calls the run() method, the implementation of run() can access the values.

com/tutego/exercise/thread/ParameterizedRunnable.java
public static Runnable getPrintingRunnable( String text, int repetitions ) {
  return () -> {
    for ( int i = 0; i < repetitions; i++ )
      System.out.printf( "%s; %s%n", text, Thread.currentThread() );

  };
}

The second proposed solution uses a factory method. As a reminder, factories are object creators and alternatives to the constructor, and in that case the parameterization is not done by a constructor, but by the method. Lambda expressions can access local variables, and parameter variables are one of them. A method can return a lambda expression in the body, and since this can fall back on the parameters, we have thus also created a parameterized Runnable.

1.6.4. Delay execution by sleeping threads.

com/tutego/exercise/thread/Sleep.java
public class Sleep {

  static long parseSleepArgument( String arg ) {
    Matcher matcher  = Pattern.compile( "(\\d+)(\\D)?" ).matcher( arg );
    boolean anyMatch = matcher.find();

    // Check if any match at all or gibberish
    if ( ! anyMatch ) {
      System.err.printf( "sleep: invalid time interval ‘%s’%n", arg );
      System.exit( 2 );
    }

    // Found at least a number, but maybe too huge to parse
    long seconds = 0;
    try { seconds = Long.parseLong( matcher.group( 1 ) ); }
    catch ( NumberFormatException e ) {
      System.err.printf( "sleep: interval to huge ‘%s’%n", arg );
      System.exit( 3 );
    }

    // Also a unit?
    String unit = matcher.group( 2 );
    if ( unit == null )
      return seconds;

    switch ( unit ) {
      case "s": break;
      case "m": seconds = TimeUnit.MINUTES.toSeconds( seconds ); break;
      case "h": seconds = TimeUnit.HOURS.toSeconds( seconds ); break;
      case "d": seconds = TimeUnit.DAYS.toSeconds( seconds ); break;
      default:
        System.err.printf( "sleep: invalid interval unit ‘%s’%n", arg );
        System.exit( 4 );
    }

    return seconds;
  }

  public static void main( String... args ) {
    if ( args.length == 0 ) {
      System.err.println( "sleep: missing operand" );
      System.exit( 1 );
    }

    long seconds = 0;
    for ( String arg : args )
      seconds += parseSleepArgument( arg );

    try { TimeUnit.SECONDS.sleep( seconds ); }
    catch ( InterruptedException e ) { /* intentionally empty */ }
  }
}

Parsing the arguments takes up the most space. We move the parsing out to a parseSleepArgument(String) method, which returns the wait time in seconds. A regular expression helps to recognize a decimal number of any length followed by a non-decimal character. Both parts are enclosed in round brackets, so that we can later access exactly this number and unit via the groups.

If the regular expression does not match, we print an error message and exit the program with System.exit(int). With a return not equal to 0 we express an error. Error-free programs generally return 0.

If there were two groups in the string, we continue. We convert the first match group to the number of seconds. This can lead to an exception if the number is too large and does not fit into a long. We have excluded letters in a number by choosing the regular expression, but a regular expression can not restrict the size. A NumberFormatException will alert us if an error occurs; then there will also be an output, and the program will terminate.

The number has been recognized, but does a unit follow? We extract the second match group, and if it is null, we can exit the method because no unit was specified. If it is not null, we have a character and check which character it is. If it was s, then we abort the switch-case and do not need to do any conversion. If it was an m, h or d, the constants in the TimeUnit enumeration help to convert it to seconds. If it was none of the four characters, there is also an error message and the program is terminated. In the best case the seconds are returned.

The main(…​) method also first performs a test to see if any arguments were passed. If not, an error message is displayed and the program is terminated with a System.exit(…​) and an error code. This is more correct than using return to exit the main(…​) method, because that would result in an exit code of 0, which signals something wrong to a calling program on the shell.

The extended for loop runs all arguments from the command line, we don`t need an index. We transfer each string to our parseSleepArgument(…​) method and sum up the result in the seconds variable. Finally, the sleep(long) method puts the main thread to sleep for the specified number of seconds. The sleep(…​) method throws an InterruptedException, a checked exception that we must handle; however, we don’t have to put anything in catch because there is no outside thread to interfere with us.

1.6.5. Watch file changes by threads

com/tutego/exercise/thread/FileChangeWatcher.java
public class FileChangeWatcher implements Runnable {

  private final Path path;
  private final Consumer<Path> callback;

  public FileChangeWatcher( String filename, Consumer<Path> callback ) {
    this.callback = Objects.requireNonNull( callback );
    path = Paths.get( filename );
  }

  @Override
  public void run() {
    try {
      FileTime oldLastModified = Files.getLastModifiedTime( path );

      while ( true ) {
        TimeUnit.MILLISECONDS.sleep( 500 );

        FileTime lastModified = Files.getLastModifiedTime( path );
        if ( ! oldLastModified.equals( lastModified ) ) {
          callback.accept( path );
          oldLastModified = lastModified;
        }
      }
    }
    catch ( Exception e ) {
      // Catch any exception and wrap in a runtime exception
      throw new RuntimeException( e );
    }
  }

  public static void main( String[] args ) {
    Consumer<Path> callback =
        path -> System.out.println( "File changed " + path );
    new Thread( new FileChangeWatcher( "c:/file.txt", callback ) ).start();
  }
}

Since the run() method of the Runnable interface has no parameter list, we have to transfer possible values or return values in a different way. The solution is quite simple: If we have a class that implements the Runnable interface, then we can use a parameterized constructor so that we can introduce state from outside. This is exactly what the constructor of FileChangeWatcher does — it takes a file name and a consumer. The constructor performs a test to check if null is passed by mistake, and then throws an exception; otherwise, the constructor stores the states in private object variables. The factory method of the Paths class automatically throws a NullPointerException, so we can save ourselves a separate null test on the filename.

The central method run() is called by the thread. The first thing we do is get the time when the file was last modified. This is the first reference point we will later compare to. All this happens in an infinite loop, because we don’t stop after one check or one detected change.

The body of the loop starts with a wait. Since we don’t want to cause a performance bottleneck by the tight loop, we let the thread sleep for half a second. Then the time of the last change is polled again and compared with the old time. If the time is not the same, then the file has changed. We have to inform our consumer in that case, call the accept(…​) method on the passed callback object and hand over the path. One should be aware that the call is synchronous and blocking. That is, if the callback method works for a very long time, our thread will not be able to continue watching the file either.

After calling the consumer, we set the old date to the current date and start comparing again in the loop.

There are several places where exceptions can occur, which is why there is a try-catch block around the loop and the initial time query. First, input/output methods throw exceptions, then sleeping. All exceptions are caught, outside the loop. This is a design decision, and you can in general do it in a different way, that the exceptions are caught inside the infinite loop, and even if exceptions are thrown, the file change continues to be checked. But if, for example, the file is deleted, the thread should also stop running. If there are exceptions, we wrap them in a RuntimeException and throw it. This will cause the thread to abort. Threads aborted by RuntimeException can be detected by a Thread.UncaughtExceptionHandler, the next exercise is about it.

A somewhat invisible place for an exception is the callback operation, which can also throw a RuntimeException. If left unhandled, it would lead to the death of the thread; however, we don`t do anything else either …​ We could also consider separating our own exceptions from the exceptions in the consumer, or reporting them with a second callback object.

1.6.6. Catch Exceptions

com/tutego/exercise/thread/GlobalExceptionHandlerDemo.java
enum GlobalExceptionHandler implements Thread.UncaughtExceptionHandler {
  INSTANCE;

  @Override public void uncaughtException( Thread thread,
                                           Throwable uncaughtException ) {
    Logger logger = Logger.getLogger( getClass().getSimpleName() );
    logger.log( Level.SEVERE, uncaughtException.getMessage()
                              + " from thread " + thread, thread );
  }
}

public class GlobalExceptionHandlerDemo {
  public static void main( String[] args ) throws Exception {
    Thread.setDefaultUncaughtExceptionHandler( GlobalExceptionHandler.INSTANCE );

    Thread zeroDivisor = new Thread( () -> System.out.println( 1 / 0 ) );
    zeroDivisor.start();

    Thread indexOutOfBound =
        new Thread( () -> System.out.println( (new int[0])[1] ) );
    indexOutOfBound.setUncaughtExceptionHandler( ( t, e ) -> {} );
    indexOutOfBound.start();

    new URL( "captain" );
  }
}

The output is:

July 04, 2020 2:07:13 PM com.tutego.exercise.thread.GlobalExceptionHandler uncaughtException
SEVERE: / by zero from thread Thread[Thread-0,5,main]
July 04, 2020 2:07:13 PM com.tutego.exercise.thread.GlobalExceptionHandler uncaughtException
SERIOUS: no protocol: captain from thread Thread[main,5,main]

An enum implements an UncaughtExceptionHandler, which together with the single static variable INSTANCE results in a singleton. The enumeration element implements the uncaughtException(…​) method from the functional interface. When activated, the JVM passes the method a reference to the dying thread and the unhandled exception. The type is throwable, which means that an error can also be reported.

The main(…​) method globally sets the UncaughtExceptionHandler, which consequently gives for all threads. The first thread will throw an ArithmeticException by dividing by 0, which our globally set UncaughtExceptionHandler will report directly.

The second thread will also be aborted by an exception, but here we have set a local UncaughtExceptionHandler via a lambda expression, which reports nothing.

The main thread also makes nonsense, because the constructor of the URL class will throw an exception with this argument. We did not catch and handle this by a try-catch block, but the main(…​) method forwards it to the JVM. This also activates the set UncaughtExceptionHandler.

1.6.7. Using thread pools

The following is part of the main(…​) method; it declares a local class DistributeGift and accesses it in code:

com/tutego/exercise/thread/GiftsInTheOrphanage.java
  class DistributeGift implements Runnable {
    private final String gift;

    public DistributeGift( String gift ) { this.gift = gift; }

    @Override public void run() {
      try {
        System.out.println( Thread.currentThread().getName()
                            + " gives " + gift );
        Thread.sleep( ThreadLocalRandom.current().nextInt( 1000, 4000 ) );
      }
      catch ( InterruptedException e ) { /* Ignore */ }
    }
  }

  Iterator<String> names =
      Arrays.asList( "Polly Zist", "Jo Ghurt", "Lisa Bonn" ).iterator();

  ExecutorService crew = Executors.newCachedThreadPool( runnable -> {
    ThreadFactory threadFactory = Executors.defaultThreadFactory();
    Thread thread = threadFactory.newThread( runnable );
    thread.setName( names.next() );
    return thread;
  } );

  String[] gifs = {
      "Dragon", "Pomsies", "Coat", "Tablet", "Doll", "Art Station",
      "Bike", "Card Game", "Slime", "Nerf Blaster" };
  for ( String gift : gifs ) {
    Thread.sleep( ThreadLocalRandom.current().nextInt( 1000, 2000 ) );
    crew.submit( new DistributeGift( gift ) );
  }
}

The Runnable is the action that is executed by the threads. Java provides two interfaces in the framework for program code to be executed: Runnable and Callable, where Callable is only used when programs want to return something in the background, this is not necessary in our case.

We can realize the Runnable simply by a lambda expression, but this is not possible here, because the Runnable must always be connected individually with a String, the gift. (This would look different if in the lambda expression the data can be obtained via a variable, but the Runnable should not obtain the data itself). Therefore, there is a class that implements Runnable and takes in the constructor the gift to be distributed. The Runnable does a screen output, sleeps for a while and then is done.

In the next step we build a thread pool. Basically, it does not matter whether the Runnable is executed by new threads or existing threads. The creation of threads costs significantly more compared to a normal object creation. To build a thread pool, we can use the parameterless method Executors.newCachedThreadPool(), or a special variant that allows us to build and parameterize the thread of the pool itself. This is not required by the task, and it has more cosmetic reasons, because this way we can set the name of the thread. In order to be able to adopt as much of the infrastructure as possible, we query the defaultThreadFactory(), create a thread via newThread(…​) and can then set the name of the thread with the familiar Thread method setName(…​). The crew member name is obtained from the Iterator via the next() method, and since the task does not require more than three threads in the thread pool, no exception occurs.

The last part is the bumping of the work packages. The program runs through the array and always creates new Runnable objects with the submitted gifts. submit(…​) passes the Runnable to the thread pool, which selects a free thread or creates one at the beginning and thus distributes the gift.

1.6.8. Get last modification of web pages

com/tutego/exercise/thread/PageLastModifiedCallableDemo.java
record WebResourceLastModifiedCallable(URL url) implements Callable<ZonedDateTime> {
  @Override public ZonedDateTime call() throws IOException {
    HttpURLConnection con = (HttpURLConnection) url.openConnection();
    long dateTime = con.getLastModified();
    con.disconnect();
    return ZonedDateTime.ofInstant( Instant.ofEpochMilli( dateTime ),
                                    ZoneId.of( "UTC" ) );
  }
}

The implementation of the Callable interface offers no surprises. The parameterized constructor takes a URL object and stores it in a private variable, making the URL accessible later in the call() method. The implementation of the call(…​) method is not much different from the template, except for the difference that we do not catch exceptions, but can easily pass them up from the call() method. Then, if an exception occurs, get(…​) is interrupted by an ExecutionException.

A usage could look like this:

com/tutego/exercise/thread/PageLastModifiedCallableDemo.java
ExecutorService executor = Executors.newCachedThreadPool();
URL url = new URL( "https://en.wikipedia.org/wiki/Main_Page" );
Callable<ZonedDateTime> callable = new WebResourceLastModifiedCallable( url );

Future<ZonedDateTime> dateTimeFuture = executor.submit( callable );

try {
  System.out.println(
      executor.submit( callable ).get( 1, TimeUnit.MICROSECONDS )
  );
}
catch ( InterruptedException | ExecutionException | TimeoutException e ) {
  e.printStackTrace();
}

try {
  ZonedDateTime wikiChangedDateTime = dateTimeFuture.get();
  System.out.println( wikiChangedDateTime );
  System.out.println(
      Duration.between( wikiChangedDateTime,
                        ZonedDateTime.now( ZoneId.of( "UTC" ) ) )
              .toMinutes()
  );
}
catch ( InterruptedException | ExecutionException e ) {
  e.printStackTrace();
}

executor.shutdown();

To dispatch Callable instances, we access a thread pool obtained via newCachedThreadPool(). The URL object denotes the Wikipedia home page, and after building a WebResourceLastModifiedCallable with this URL, we can call the submit(..) method with the Callable object. This alone does not throw any exceptions (apart from the parameterized URL constructor), exception handling is only needed by retrieving the result with get(…​).

The example implements two scenarios: The first submit(…​) lets a Callable be executed by the thread pool, it then works in the background. The second submit(…​) also puts a Callable into the thread pool, but only gives get(…​) a microsecond to complete; this is obviously not enough, and there is a TimeoutException.

The second try-catch block is for the first Callable sent. The dateTimeFuture is queried in a blocking manner with get() for the result, and a few milliseconds should have already passed for the runtime environment to handle the previous requests. If we compare the catch blocks, we find that an additional TimeoutException must be handled if there is a time bound. In the case with the parameterless get() however not; it remains with an InterruptedException and ExecutionException.

For the calculation of the difference we use the method between(…​) of the class Duration. The static method gets two arguments: once the time from the Future and the current time. We must remember not to just ask for the current time with now(), but we must ask for the current time in the UTC time zone, because otherwise the difference would be wrong, unless by chance the application itself is in UTC ± 0.

1.6.9. Writing memories into a poetry album

The problem is two threads accessing a shared resource. Both access the resource concurrently and mess up the output. The output might look like this:

DWeinen Bdulu mlaenc hbsrt,au lcahcenhe Sno nsneien saclhelei.n Wuennd n icduh w ebriansuct,h wCeainpastta diun aClileoiCinaeo
_,.-'~'-.,__,.-'~'-.,__,.-'~'-.,__,.-'~'-.,__,.-'~'-.,_
 to be happy
_,.-'~'-.,__,.-'~'-.,__,.-'~'-.,__,.-'~'-.,__,.-'~'-.,_

Appending letters is a critical section that must be protected. synchronized blocks are somewhat outdated and we want to use Lock objects. For our use case, the ReentrantLock implementation is suitable.

com/tutego/exercise/thread/WriteInFriendshipBook.java
Lock lock = new ReentrantLock( true );

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

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

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

Threads coordinate with each other via the Lock objects. When one thread enters the critical section, another thread will have to wait until the critical section is unlocked. To enter and exit the block, there are two central methods in Lock: lock() and unlock(). The constructor of ReentrantLock is overloaded, and the parameterized variant uses a boolean parameter to determine whether the Lock is fair or not. Fair in this context means that the threads that wait the longest are allowed to enter the released block first. Otherwise, the behavior is non-deterministic. This is also one of the differences from the synchronized keyword, where the virtual machine can choose any thread. Whether the allocation is fair or not depends on the implementation of the JVM, and the fairness in synchronized is not controllable.

The proposed solution has adapted the source code a bit. One place is outside the Author class declaration, because the Lock object must be available to all threads, since that is what the threads coordinate against. The second change is inside the run() method, because the operation to be saved is writing to the journal. Before the loop the block is closed with lock(), then all characters are appended, the separator is written and finally unlock() is called again, which releases the block for the next thread. An unlock() should always be in a finally block, because if a Lock is requested, it should always be released, even if there is a return or an exception that exits the method. finally blocks are always processed, regardless of whether there was an exception or not.

Besides the lock() method there is a second method lockInterruptibly() which can be interrupted by an interrupt from outside. lock() does not react to an interrupt from outside, that means the catch of the InterruptedException is only valid for the sleep(…​) method.

1.6.10. Attending the Banquet with the Captains — Semaphore

The following is part of the main(…​) method:

com/tutego/exercise/thread/Banquet.java
  Semaphore seats = new Semaphore( 6 - 2 );

  class Guest implements Runnable {
    private final String name;
    public Guest( String name ) { this.name = name; }

    @Override
    public void run() {
      try {
        System.out.printf( "%s is waiting for a free place%n", name );
        seats.acquire();
        System.out.printf( "%s has a seat at the table%n", name );
        Thread.sleep( ThreadLocalRandom.current().nextInt( 2000, 5000 ) );
      }
      catch ( InterruptedException e ) { /* Ignore */ }
      finally {
        System.out.printf( "%s leaves the table%n", name );
        seats.release();
      }
    }
  }

  List<String> names = new ArrayList<>( Arrays.asList(
      "Balronoe", "Xidrora", "Zobetera", "Kuecarro", "Bendover",
      "Bane", "Cody", "Djarin", "Enfy"
  ) );
  for ( int i = 0, len = names.size(); i < len; i++ )
    Collections.addAll( names, "Admiral " + names.get( i ),
                               "Commander " + names.get( i ) );

  ExecutorService executors = Executors.newCachedThreadPool();

  for ( String name : names )
    executors.execute( new Guest( name ) );

  executors.shutdown();
}

The class Banquet consists in its core of four parts:

  1. the construction of the Semaphore.

  2. the declaration of a guest, a Runnable, which refers back to the Semaphore.

  3. the construction of names

  4. the creation of the Runnable instances and execution over a thread pool

Since there are six seats at the table and two seats are already occupied by Bonny Brain and Captain CiaoCiao, that leaves four seats that different guests can switch between.

All guests have a name, which is stored in the object. The name appears three times in the output:

  1. At the beginning the name of the guest is printed, because the guest is waiting and may not have a place yet. A thread must first get permission from the Semaphore using acquire(); the method blocks if the maximum number of four free seats has already been reached. In the console output it can be seen that everyone waits for a free seat first and the first four guests immediately get a seat as well.

  2. If acquire() is unblocked, the thread can spend time at the table with the captains together with the other four threads.

  3. After a waiting period the finally block is processed and the name of the guest is printed again, because the guest now leaves the table. It is important to call the release() method on the Semaphore, so that other waiting guests can come to the table.

The demo data is generated by a simple algorithm. A list is pre-populated with some strings. This list is now run with the original length, and new strings are generated starting with Admiral in the front, and more strings starting with Commander.

Now only the threads have to be generated, and the ExecutorService can start them.

1.6.11. Swearing and insulting — Condition

The code is part of the main(…​) method:

com/tutego/exercise/thread/InsultSwordFighting.java
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

class Insulter implements Runnable {
  private final String[] insults;

  public Insulter( String[] insults ) {
    this.insults = insults;
  }

  @Override public void run() {
    while ( Thread.currentThread().isInterrupted() ) {
      lock.lock();
      try {
        Thread.sleep( ThreadLocalRandom.current().nextInt( 1000 ) );
        String name = Thread.currentThread().getName();
        int rndInsult = ThreadLocalRandom.current().nextInt( insults.length );
        System.out.println( name + ": " + insults[ rndInsult ] + '!' );
        condition.signal();
        condition.await();
      }
      catch ( InterruptedException e ) { Thread.currentThread().interrupt(); }
      finally {
        lock.unlock();
      }
    }
  }
}

String[] insults1 = {
    "Trollop", "You have the manners of a trump",
    "You fight like a cow cocky", "Prat",
    "Your face makes onions cry",
    "You are so full of s**t, the toilet’s jealous"
};
String[] insults2 = {
    "Wazzock", "I've spoken with rats more polite than you",
    "Chuffer", "You make me want to spew",
    "Check your lipstick before you come for me",
    "You are more disappointing than an unsalted pretzel"
};

new Thread( new Insulter( insults1 ), "pirate-1" ).start();
new Thread( new Insulter( insults2 ), "pirate-2" ).start();

The solution relies on Condition, and that has two important methods: for notifying other waiting threads and for waiting for a notification.

In our case, the Insulter is a Runnable that is initialized with different curses via the constructor. The run() method contains an infinite loop, which in principle can be terminated with an interrupt, because insulting knows no end, and every action is followed by a reaction. A Condition object may only be used if lock() marks a critical section — Condition objects originate from a Lock object.

After the time of thinking, a random cussword is selected and output, and then signal() is used to inform the other thread. With the following await() we wait again in the thread for the signal of the other thread.

The flow will be as follows: main(…​) will start two Insulter threads, and one of the two threads will enter the area first via the Lock object, then lock and claim the block exclusively for itself. The second thread will come in a little later, and hang in the Lock and get no further. The first thread then starts cursing and finally signals the other waiting thread that it is done. After signaling, the first thread also waits for a signal. It is important to understand that the associated lock is temporarily released so that the other thread can run into the protected block, receive the signal, and then in turn perform the action. While one thread waits in await(), the other thread performs its operation and later sets a signal again. This wakes up the waiting thread again, it gets the lock back and continues its operation while the other side waits.

1.6.12. Take pens out of paintbox — Condition

Let’s start with the Paintbox class. The maximum number of pens initializes the object variable freeNumberOfPens, which is decreased or increased below.

com/tutego/exercise/thread/Kindergarten.java
class Paintbox {

  private int freeNumberOfPens;
  private final Lock lock = new ReentrantLock();
  private final Condition condition = lock.newCondition();

  public Paintbox( int maximumNumberOfPens ) {
    freeNumberOfPens = maximumNumberOfPens;
    System.out.printf( "Paintbox equipped with %s pens%n", freeNumberOfPens );
  }

  public void acquirePens( int numberOfPens ) {
    lock.lock();
    try {
      while ( freeNumberOfPens < numberOfPens ) {
        System.out.printf( "%d pens from paintbox requested, available only %d,"
                           + " someone has to wait :(%n",
                           numberOfPens, freeNumberOfPens );
        condition.await();
      }

      freeNumberOfPens -= numberOfPens;
    }
    catch ( InterruptedException e ) { Thread.currentThread().interrupt(); }
    finally {
      lock.unlock();
    }
  }

  public void releasePens( int numberOfPens ) {
    lock.lock();
    try {
      freeNumberOfPens += numberOfPens;
      condition.signalAll();
    }
    finally {
      lock.unlock();
    }
  }
}

acquirePens(int numberOfPens) is called by the children who want to take a certain number of pens from the paint box. The operation takes place in a critical section, so Lock is used to lock the section for other threads. A loop condition checks if the number of free pens is less than the number of desired pens, and if it is, then it must wait. If a signal comes later, it must be asked repeatedly whether this condition still applies. It is a programming error if an if statement is used instead of a loop. After the end of the while loop the number of free pins is reduced and the critical section is released again. Consider: when waiting for a signal, the lock is temporarily released.

releasePens(int numberOfPens) is easier: it increases the number of free pens and then signals other waiting threads that pens are available again. signalAll() signals all waiting threads. Signaling, just like waiting, must occur in a locked section, which is terminated in a finally block with unlock() as usual.

The class Child implements Runnable and takes the name of the child and the paint box in the constructor.

com/tutego/exercise/thread/Kindergarten.java
class Child implements Runnable {
  private final String   name;
  private final Paintbox paintbox;

  public Child( String name, Paintbox paintbox ) {
    this.name     = name;
    this.paintbox = paintbox;
  }

  @Override
  public void run() {
    while ( ! Thread.currentThread().isInterrupted() ) {
      ThreadLocalRandom random = ThreadLocalRandom.current();
      int requiredPens = random.nextInt( 1, 10 + 1 );
      paintbox.acquirePens( requiredPens );
      System.out.printf( "%s got %d pens%n", name, requiredPens );

      try {
        TimeUnit.MILLISECONDS.sleep( random.nextInt( 1000, 3000 ) );
      }
      catch ( InterruptedException e ) { Thread.currentThread().interrupt(); }

      paintbox.releasePens( requiredPens );
      System.out.printf( "%s returned %d pens%n", name, requiredPens );

      try {
        TimeUnit.SECONDS.sleep( random.nextInt( 1, 5 + 1 ) );
      }
      catch ( InterruptedException e ) { Thread.currentThread().interrupt(); }
    }
  }
}

The run() method runs indefinitely in theory, unless the thread is terminated externally with an interrupt. The following happens in the body of the while loop:

  1. A random number is calculated for the number of desired pins.

  2. On the paint box acquirePens(…​) is called to get this number of pens. A waiting situation may occur here if the desired number of pens is not available.

  3. If the pens are available acquirePens(…​) returns and the requested number of pens are printed.

  4. There is a wait, because the child is drawing.

  5. The pens are put back with releasePens(…​). Putting back never blocks for a long time.

  6. It waits again, and a new loop cycle can begin.

1.6.13. Play Rock, Paper, Scissors — CyclicBarrier

The code is part of the main(…​) method:

com/tutego/exercise/thread/RockPaperScissorsHandGame.java
Queue<HandSign> handSigns = new ArrayBlockingQueue<>( 2 );

Runnable determineWinner = () -> {
  HandSign handSign1 = handSigns.poll();
  HandSign handSign2 = handSigns.poll();

  switch ( handSign1.beats( handSign2 ) ) {
    case  0 -> System.out.printf( "Tie, both players choose %s%n", handSign1 );
    case +1 -> System.out.printf( "Player 1 wins with %s, player 2 loses with %s%n",
                                  handSign1, handSign2 );
    case -1 -> System.out.printf( "Player 2 wins with %s, player 1 loses with %s%n",
                                  handSign2, handSign1 );
  }
};

CyclicBarrier barrier = new CyclicBarrier( 2, determineWinner );

Runnable playScissorsRockPaper = () -> {
  try {
    handSigns.add( HandSign.random() );
    barrier.await();
  }
  catch ( InterruptedException | BrokenBarrierException e ) { /* Ignore */ }
};

ScheduledExecutorService executor = Executors.newScheduledThreadPool( 2 );
executor.scheduleAtFixedRate( () -> {
  System.out.println( "Schnick, Schnack, Schnuck" );
  executor.execute( playScissorsRockPaper );
  executor.execute( playScissorsRockPaper );
}, 0, 1, TimeUnit.SECONDS );

In the proposed solution we have two different Runnable types. One Runnable evaluates the winner, and the other Runnable executes the hand signal. The Runnable are implemented by lambda expressions and access a common data structure handSigns. Each player makes a random hand sign, places it in the Queue and waits for the barrier to end.

The barrier itself is initialized with size 2 and initialized with the Runnable determineWinner, which is executed whenever it came to the second call of await() on the barrier. The determineWinner takes the two hand signs out of the queue, uses beats(…​) to determine which player scored in which way, and prints a console message about the winner, loser or tie.

The ScheduledExecutorService helps to replay the actual game every second and run the Runnable behind playScissorsRockPaper twice.

Java 8 backport

The proposed solution uses the switch statement, which has made its way into Java 14. For Java 8, this can be easily rewritten.

1.6.14. Find the fastest runner — CountDownLatch

The following code is in the main(…​) method:

com/tutego/exercise/thread/SprintRace.java
final int NUMBER_OF_ATHLETES = 10;
CountDownLatch startLatch = new CountDownLatch( 1 );
CountDownLatch endLatch   = new CountDownLatch( NUMBER_OF_ATHLETES );

ConcurrentNavigableMap<Integer, String> records =
    new ConcurrentSkipListMap<>();

Runnable athlete = () -> {
  try {
    startLatch.await();
    int time = ThreadLocalRandom.current().nextInt( 1_000, 2_000 );
    TimeUnit.MILLISECONDS.sleep( time );
    records.put( time, Thread.currentThread().getName() );
    endLatch.countDown();
  }
  catch ( InterruptedException e ) { /* Ignore */ }
};

for ( int i = 0; i < NUMBER_OF_ATHLETES; i++ )
  new Thread( athlete, "athlete-" + (i + 1) ).start();

// Start the race
startLatch.countDown();

// Wait for race to end
endLatch.await();

records.forEach(
  (time, name) -> System.out.printf( "%s in %d ms%n", name, time )
);

The solution uses two CountDownLatch objects. The first startLatch we initialize with 1, and when multiple threads are started, they wait for this start CountDownLatch to become 0. The second CountDownLatch endLatch we initialize with 10, the number of athletes, and whenever an athlete reaches the finish line, the endLatch is decreased.

For sorting by run times, we resort to a sorted associative memory. Since the threads write concurrently to the data structure, the data structure must support this type of access. For this the package java.util.concurrent offers various data structures. We use a ConcurrentSkipListMap, an associative memory that can handle concurrent writes. The class implements the interface ConcurrentNavigableMap.

The athletes are threads that are given a simple name via the constructor of the Thread class. After the threads are started, they all wait at the startLatch to reach 0. The call startLatch.countDown() fires the starting gun, and all the athlete threads start running. Each of the threads waits a random time, then writes itself to the data structure and decrements the counter in endLatch. The main program also waits with an endLatch.await() to unblock, and this is achieved by all athletes decrementing the counter so that it becomes 0. After the end of the run, forEach(…​) iterates over the map and outputs the time along with the name.