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, whereasi++
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 ofExecutorService
) with a fixed number of threads (specified as argument),Executors.newCachedThreadPool
returns a pool (also instance ofExecutorService
), 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.