Abstractions

Abstractions #

We review here some higher-level utilities available in Java to use multithreading, while reducing the risk of unwanted behaviors.

Thread safety #

Terminology. A method is said to be thread-safe if it can be accessed concurrently by several threads without “unexpected” consequences.

Thread safety is a vague term, which may for instance refer to implementations that are free of race conditions.

Most high-level libraries or frameworks that exploit concurrency provide methods with some from of thread safety. In particular, this is the case of most Graphical User Interface frameworks (such as JavaFX or Swing for Java).

By default, it is highly recommended to rely on such methods when using concurrency. For instance, Effective Java (Item 81) recommends using thread-safe concurrency utilities when possible, rather than Java’s (error prone) wait, notify and notifyAll methods.

Atomic operation #

Terminology. An operation performed by a thread is atomic if no operation performed by another (concurrent or parallel) thread can affect its execution.

We saw earlier that the operation

i++;

is not atomic, but corresponds to a sequence of instructions (fetch, increment and write).

The package java.util.concurrent.atomic provides classes like AtomicInteger, AtomicBoolean or AtomicReference that support atomic operations.

Example. The program

AtomicInteger i = new AtomicInteger(0);
int j = i.getAndIncrement();

is analogous to

int i = 0;
int j = i++;

but the operation i.getAndIncrement() is atomic, whereas i++ is not.

Associative array #

ConcurrentMap #

The interface ConcurrentMap is a sub-interface of Map (which stands for an associative array) with additional atomicity and thread-safety guarantees.

It has two native implementations, one as a hash table, the other as a so-called skip list. The latter maintains the order of insertion, analogously to what we saw for a LinkedHashMap.

ConcurrentNavigableMap #

The interface ConcurrentNavigableMap is a sub-interface of SortedMap (like TreeMap, that we saw in the dedicated section), with additional atomicity and thread-safety guarantees. It has one native implementations, as a skip list.

Queue #

The Java interface BlockingQueue is a sub-interface of Queue (which stands for the abstract data type queue). In addition to the traditional operations supported by a queue (dequeue and enqueue), a blocking queue provides variants of these operations to:

  • wait for the queue to become non-empty (when dequeuing), or
  • wait for space to be freed in the queue (when enqueueing).

These allow implementing the producer/consumer pattern seen earlier, without relying on Java’s wait, notify and notifyAll methods.

BlockingQueue has several native implementations (e.g. as a doubly linked list or as a dynamic array).

Thread pool #

A thread pool is a set of threads (of fixed or flexible size) that can be reused for different tasks.

The factory class Executors provides a series of static methods that return a thread pool. In particular:

  • Executors.newFixedThreadPool returns a pool (instance of ExecutorService) with a fixed number of threads (specified as argument),
  • Executors.newCachedThreadPool returns a pool (also instance of ExecutorService), but with flexible size.

In both cases, tasks submitted to the pool will reuse existing threads if available.

A quick introduction to Java thread pools can be found here.