Chapter 7 introduced Java’s Threads API. Significant problems with Threads resulted in the development of the more powerful Concurrency Utilities framework. In this chapter, we take you on a tour of this framework from Android’s perspective.
Introducing the Concurrency Utilities
Low-level concurrency primitives, such as synchronized and wait()/notify(), are often hard to use correctly. Incorrect use of these primitives can result in race conditions, thread starvation, deadlock, and other hazards, which can be hard to detect and debug.
Too much reliance on the synchronized primitive can lead to performance issues, which affect an application’s scalability. This is a significant problem for highly threaded applications such as web servers.
Developers often need to use higher-level constructs such as thread pools and semaphores. Because these constructs aren’t included with Java’s low-level threading capabilities, developers have been forced to build their own constructs, which is a time-consuming and error-prone activity.
java.util.concurrent contains utility types that are often used in concurrent programming, for example, executors, thread pools, and concurrent hashmaps.
java.util.concurrent.atomic contains utility classes that support lock-free, thread-safe programming on single variables.
java.util.concurrent.locks contains utility types for locking and waiting on conditions (objects that let threads suspend execution [wait] until notified by other threads that some Boolean state may now be true). Locking and waiting via these types provides better performance and is more flexible than doing so via Java’s monitor-based synchronization and wait/notification mechanisms.
This framework also introduces a long nanoTime() method to the java.lang.System class, which lets you access a nanosecond-granularity time source for making relative time measurements.
Two terms that are commonly encountered when exploring the Concurrency Utilities framework are parallelism and concurrency. According to Oracle’s “Multithreading Guide” (http://docs.oracle.com/cd/E19455-01/806-5257/6je9h032b/index.html), parallelism is “a condition that arises when at least two threads are executing simultaneously.” In contrast, concurrency is “a condition that exists when at least two threads are making progress [simultaneously or not. It is a] more generalized form of parallelism that can include time-slicing as a form of virtual parallelism.”
The concurrency utilities can be classified as executors, synchronizers, concurrent collections, a locking framework, and atomic variables. We explore each category in the following sections.
Exploring Executors
The Threads API lets you execute runnable tasks via expressions such as new java.lang.Thread(new RunnableTask()).start();. These expressions tightly couple task submission with the task’s execution mechanics (run on the current thread, a new thread, or a thread arbitrarily chosen from a pool [group] of threads).
A task is an object whose class implements the java.lang.Runnable interface (a runnable task) or the java.util.concurrent.Callable interface (a callable task).
The concurrency-oriented utilities provide executors as a high-level alternative to low-level Threads API expressions for executing runnable tasks. An executor is an object whose class directly or indirectly implements the java.util.concurrent.Executor interface, which decouples task submission from task-execution mechanics.
The executor framework’s use of interfaces to decouple task submission from task-execution mechanics is analogous to the Collections Framework’s use of core interfaces to decouple lists, sets, queues, and maps from their implementations. Decoupling results in flexible code that’s easier to maintain.
Executor declares a solitary void execute(Runnable runnable) method that executes the runnable task named runnable at some point in the future. execute() throws java.lang.NullPointerException when runnable is null and java.util.concurrent.RejectedExecutionException when it cannot execute runnable.
RejectedExecutionException can be thrown when an executor is shutting down and doesn’t want to accept new tasks. Also, this exception can be thrown when the executor doesn’t have enough room to store the task (perhaps the executor uses a bounded blocking queue to store tasks and the queue is full; we discuss blocking queues later in this chapter).
Executor focuses exclusively on Runnable. Because Runnable’s run() method doesn’t return a value, there’s no convenient way for a runnable task to return a value to its caller.
Executor doesn’t provide a way to track the progress of runnable tasks that are executing, cancel an executing runnable task, or determine when the runnable task finishes execution.
Executor cannot execute a collection of runnable tasks.
Executor doesn’t provide a way for an application to shut down an executor (much less to shut down an executor properly).
These limitations are addressed by the java.util.concurrent.ExecutorService interface, which extends Executor and whose implementation is typically a thread pool.
The API documentation describes all this interface’s methods. There you also learn that the interface handles callable tasks, which are analogous to runnable tasks. Unlike Runnable, whose void run() method cannot throw checked exceptions, Callable<V> declares a V call() method that returns a value and which can throw checked exceptions because call() is declared with a throws Exception clause.
Finally, the interface exhibits the Future interface, which represents the result of an asynchronous computation. The result is known as a future, because it typically will not be available until some moment in the future. Future, whose generic type is Future<V>, provides methods for canceling a task, for returning a task’s value, and for determining whether or not the task has finished.
Suppose that you intend to write an application whose graphical user interface lets the user enter a word. After the user enters the word, the application presents this word to several online dictionaries and obtains each dictionary’s entry. These entries are subsequently displayed to the user.
After obtaining an executor in some manner, the example’s main thread submits a callable task to the executor. The submit() method immediately returns with a reference to a Future object for controlling task execution and accessing results. The main thread ultimately calls this object’s get() method to get these results.
The Executors utility class declares several class methods that return instances of various ExecutorService and ScheduledExecutorService implementations (and other kinds of instances).
For example, static ExecutorService newFixedThreadPool(int nThreads) creates a thread pool that reuses a fixed number of threads operating off of a shared unbounded queue. At most, nThreads threads are actively processing tasks. If additional tasks are submitted when all threads are active, they wait in the queue for an available thread.
Thread pools are used to eliminate the overhead from having to create a new thread for each submitted task. Thread creation isn’t cheap, and having to create many threads could severely impact an application’s performance.
You would commonly use executors, runnables, callables, and futures in file and network input/output contexts. Performing a lengthy calculation offers another scenario where you could use these types.
Exploring Synchronizers
The Threads API offers synchronization primitives for synchronizing thread access to critical sections. Because it can be difficult to write synchronized code correctly that’s based on these primitives, Concurrency Utilities includes synchronizers, classes that facilitate common forms of synchronization.
Four commonly used synchronizers are countdown latches, cyclic barriers, exchangers, and semaphores. We explore each synchronizer in this section.
Countdown Latches
A countdown latch causes one or more threads to wait at a “gate” until another thread opens this gate, at which point these other threads can continue. It consists of a count and operations for “causing a thread to wait until the count reaches zero” and “decrementing the count.”
The java.util.concurrent.CountDownLatch class implements the countdown latch synchronizer. You initialize a CountDownLatch instance to a specific count by invoking this class’s CountDownLatch(int count) constructor. This constructor throws IllegalArgumentException when the value passed to count is negative.
Cyclic Barriers
A cyclic barrier lets a set of threads wait for each other to reach a common barrier point. The barrier is cyclic because it can be reused after the waiting threads are released. This synchronizer is useful in applications involving a fixed-size party of threads that must occasionally wait for each other.
The java.util.concurrent.CyclicBarrier class implements the cyclic barrier synchronizer. You initialize a CyclicBarrier instance to a specific number of parties (threads working toward a common goal) by invoking this class’s CyclicBarrier(int parties) constructor. This constructor throws IllegalArgumentException when the value passed to parties is less than 1.
Alternatively, you can invoke the CyclicBarrier(int parties, Runnable barrierAction) constructor to initialize a cyclic barrier to a specific number of parties and a barrierAction that’s executed when the barrier is tripped. In other words, when parties - 1 threads are waiting and one more thread arrives, that thread executes barrierAction and then all threads proceed. This runnable is useful for updating shared state before any of the threads continue. This constructor throws IllegalArgumentException when the value passed to parties is less than 1.
Exchangers
An exchanger provides a synchronization point where threads can swap objects. Each thread presents some object on entry to the exchanger’s exchange() method, matches with a partner thread, and receives its partner’s object on return. Exchangers can be useful in applications such as genetic algorithms (see http://en.wikipedia.org/wiki/Genetic_algorithm) and pipeline designs.
The generic java.util.concurrent.Exchanger<V> class implements the exchanger synchronizer. You initialize an exchanger by invoking the Exchanger() constructor.
Semaphores
A semaphore maintains a set of permits for restricting the number of threads that can access a limited resource. A thread attempting to acquire a permit when no permits are available blocks until some other thread releases a permit.
Semaphores whose current values can be incremented past 1 are known as counting semaphores, whereas semaphores whose current values can be only 0 or 1 are known as binary semaphores or mutexes. In either case, the current value cannot be negative.
The java.util.concurrent.Semaphore class implements this synchronizer and conceptualizes a semaphore as an object maintaining a set of permits. You initialize a semaphore by invoking the Semaphore(int permits) constructor where permits specifies the number of available permits. The resulting semaphore’s fairness policy is set to false (unfair). Alternatively, you can invoke the Semaphore(int permits, boolean fair) constructor to also set the semaphore’s fairness setting to true (fair).
Exploring the Concurrent Collections
In Chapter 9, we introduced you to the Collections Framework. This framework provides interfaces and classes that are located in the java.util package. Interfaces include List, Set, and Map; classes include ArrayList, TreeSet, and HashMap.
ArrayList, TreeSet, HashMap, and other implementation classes are not thread-safe. However, you can make them thread-safe by using the synchronized wrapper methods located in the java.util.Collections class. For example, you can pass an ArrayList instance to Collections.synchronizedList() to obtain a thread-safe variant of ArrayList.
It’s necessary to acquire a lock before iterating over a collection that might be modified by another thread during the iteration. If a lock isn’t acquired and the collection is modified, it’s highly likely that java.util.ConcurrentModificationException will be thrown. This happens because Collections Framework classes return fail-fast iterators, which are iterators that throw ConcurrentModificationException when collections are modified during iteration. Fail-fast iterators are often inconvenient to concurrent applications.
Performance suffers when synchronized collections are accessed frequently from multiple threads. This performance problem ultimately impacts an application’s scalability.
An element that’s removed after iteration starts but hasn’t yet been returned via the iterator’s next() method won’t be returned.
An element that’s added after iteration starts may or may not be returned.
No element is returned more than once during the collection’s iteration, regardless of changes made to the collection during iteration.
BlockingQueue is a subinterface of java.util.Queue that also supports blocking operations that wait for the queue to become nonempty before retrieving an element and wait for space to become available in the queue before storing an element. Each of the ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, and SynchronousQueue classes implements this interface.
ConcurrentMap is a subinterface of java.util.Map that declares additional atomic putIfAbsent(), remove(), and replace() methods. The ConcurrentHashMap class (the concurrent equivalent of java.util.HashMap), the ConcurrentNavigableMap class, and the ConcurrentSkipListMap class implement this interface.
Oracle’s Javadoc for BlockingQueue, ArrayBlockingQueue, and other concurrency-oriented collection types identifies these types as part of the Collections Framework.
Exploring the Locking Framework
The java.util.concurrent.locks package provides a framework of interfaces and classes for locking and waiting for conditions in a manner that’s distinct from built-in synchronization and java.lang.Object’s wait/notification mechanism. The locking framework improves on synchronization and wait/notification by offering capabilities such as lock polling and timed waits.
The locking framework includes the often-used Lock, ReentrantLock, Condition, ReadWriteLock, and ReentrantReadWriteLock types, which we discuss in this section.
Lock
void lock(): Acquires the lock. When the lock isn’t available, the calling thread is forced to wait until it becomes available.
void lockInterruptibly(): Acquires the lock unless the calling thread is interrupted. When the lock isn’t available, the calling thread is forced to wait until it becomes available or the thread is interrupted, which results in this method throwing InterruptedException.
Condition newCondition(): Returns a new Condition instance that’s bound to this Lock instance. This method throws java.lang.UnsupportedOperationException when the Lock implementation doesn’t support conditions.
boolean tryLock(): Acquires the lock when it’s available at the time this method is invoked. The method returns true when the lock is acquired and false when the lock isn’t acquired.
boolean tryLock(long time, TimeUnit unit): Acquires the lock when it’s available within the specified waiting time and the calling thread isn’t interrupted. When the lock isn’t available, the calling thread is forced to wait until it becomes available within the waiting time or the thread is interrupted, which results in this method throwing InterruptedException.
void unlock(): Releases the lock.
Acquired locks must be released. In the context of synchronized methods and statements, and the implicit monitor lock associated with every object, all lock acquisition and release occurs in a block-structured manner. When multiple locks are acquired, they’re released in the opposite order and all locks are released in the same lexical scope in which they were acquired.
This idiom ensures that an acquired lock will always be released.
ReentrantLock
Lock is, for example, implemented by the ReentrantLock class, which describes a reentrant mutual exclusion lock. This lock is associated with a hold count. When a thread holds the lock and reacquires the lock by invoking lock(), lockUninterruptibly(), or one of the tryLock() methods, the hold count is increased by 1. When the thread invokes unlock(), the hold count is decremented by 1. The lock is released when this count reaches 0.
For more details, please see the API documentation.
Condition
The Condition interface factors out Object’s wait and notification methods (wait(), notify(), and notifyAll()). So we have distinct objects which correspond with the use of arbitrary lock implementations.
A Condition instance is intrinsically bound to a lock. To obtain a Condition instance for a particular Lock instance, use Lock’s newCondition() method.
void await(): Forces the calling thread to wait until it’s signaled or interrupted
boolean await(long time, TimeUnit unit): Forces the calling thread to wait until it’s signaled or interrupted, or until the specified waiting time elapses
long awaitNanos(long nanosTimeout): Forces the current thread to wait until it’s signaled or interrupted, or until the specified waiting time elapses
void awaitUninterruptibly(): Forces the current thread to wait until it’s signaled
boolean awaitUntil(Date deadline): Forces the current thread to wait until it’s signaled or interrupted, or until the specified deadline elapses
void signal(): Wakes up one waiting thread
void signalAll(): Wakes up all waiting threads
ReadWriteLock
Situations arise where data structures are read more often than they’re modified. For example, you may have created an online dictionary of word definitions that many threads will read concurrently, whereas a single thread may add new definitions or update existing definitions. The locking framework provides a read-write locking mechanism for these situations that yields greater concurrency when reading and the safety of exclusive access when writing. This mechanism is based on the ReadWriteLock interface.
ReadWriteLock maintains a pair of locks: one lock for read-only operations and one lock for write operations. Multiple reader threads may hold the read lock simultaneously, as long as there are no writers. The write lock is exclusive: only a single thread can modify shared data. (The lock that’s associated with the synchronized keyword is also exclusive.)
Lock readLock(): Returns the lock that’s used for reading
Lock writeLock(): Returns the lock that’s used for writing
ReentrantReadWriteLock
ReadWriteLock is implemented by the ReentrantReadWriteLock class, which describes a reentrant read-write lock with similar semantics to ReentrantLock.
ReentrantReadWriteLock(): Creates an instance of ReentrantReadWriteLock. This constructor is equivalent to ReentrantReadWriteLock(false).
ReentrantReadWriteLock(boolean fair): Creates an instance of ReentrantReadWriteLock with the specified fairness policy. Pass true to fair when this lock should use a fair ordering policy.
ReentrantReadWriteLock.ReadLock readLock(): Returns the lock used for reading
ReentrantReadWriteLock.WriteLock writeLock(): Returns the lock used for writing
int getReadHoldCount(): Returns the number of reentrant read holds on this lock by the calling thread, which is 0 when the read lock isn’t held by the calling thread. A reader thread has a hold on a lock for each lock action that’s not matched by an unlock action.
int getWriteHoldCount(): Returns the number of reentrant write holds on this lock by the calling thread, which is 0 when the write lock isn’t held by the calling thread. A writer thread has a hold on a lock for each lock action that’s not matched by an unlock action.
Exploring Atomic Variables
The java.util.concurrent.atomic package provides a small toolkit of classes that support lock-free, thread-safe operations on single variables. The classes in this package extend the notion of volatile values, fields, and array elements to those that also provide an atomic conditional update so that external synchronization isn’t required.
AtomicBoolean: A boolean value that may be updated atomically
AtomicInteger: An int value that may be updated atomically
AtomicIntegerArray: An int array whose elements may be updated atomically
AtomicLong: A long value that may be updated atomically
AtomicLongArray: A long array whose elements may be updated atomically
AtomicReference: An object reference that may be updated atomically
AtomicReferenceArray: An object reference array whose elements may be updated atomically
Returning Unique Identifiers in a Thread-Safe Manner via synchronized
Returning Unique IDs in a Thread-Safe Manner via AtomicLong
In Listing 11-2, we’ve converted nextID from a long to an AtomicLong instance, initializing this object to 0. We’ve also refactored the getNextID() method to call AtomicLong’s getAndIncrement() method, which increments the AtomicLong instance’s internal long integer variable by 1 and returns the previous value in one indivisible step.
- 1.
Define Concurrency Utilities.
- 2.
Identify the packages in which Concurrency Utilities types are stored.
- 3.
Define task.
- 4.
Define executor.
- 5.
Identify the Executor interface’s limitations.
- 6.
What differences exist between Runnable’s run() method and Callable’s call() method?
- 7.
Define future.
- 8.
Describe the Executors class’s newFixedThreadPool() method.
- 9.
Define synchronizer.
- 10.
What concurrency-oriented extensions to the Collections Framework are provided by the Concurrency Utilities?
- 11.
Define lock.
- 12.
What is the biggest advantage that Lock objects hold over the implicit locks that are obtained when threads enter critical sections (controlled via the synchronized reserved word)?
- 13.
How do you obtain a Condition instance for use with a particular Lock instance?
- 14.
Define atomic variable.
- 15.
What does the AtomicIntegerArray class describe?
- 16.
Convert the following expressions to their atomic variable equivalents:
int total = ++counter;
int total = counter--;
Summary
The low-level Threads API lets you create multithreaded applications that offer better performance and responsiveness over their single-threaded counterparts. However, performance issues that affect an application’s scalability and other problems resulted in Java 5’s introduction of the Concurrency Utilities.
The Concurrency Utilities organizes its many types into three packages: java.util.concurrent, java.util.concurrent.atomic, and java.util.concurrent.locks. Basic types, such as executors, thread pools, and concurrent hashmaps, are stored in java.util.concurrent; classes that support lock-free, thread-safe programming on single variables are stored in java.util.concurrent.atomic; and types for locking and waiting on conditions are stored in java.util.concurrent.locks.
An executor decouples task submission from task-execution mechanics and is described by the Executor, ExecutorService, and ScheduledExecutorService interfaces. You obtain an executor by calling one of the utility methods in the Executors class. Executors are associated with callables and futures.
A synchronizer facilitates common forms of synchronization. Countdown latches, cyclic barriers, exchangers, and semaphores are commonly used synchronizers.
A concurrent collection is an extension to the Collections Framework. The BlockingQueue and ConcurrentMap interfaces along with the ArrayBlockingQueue and ConcurrentHashMap classes are examples.
The java.util.concurrent.locks package provides a framework of interfaces and classes for locking and waiting for conditions in a manner that’s distinct from built-in synchronization and Object’s wait/notification mechanism. Java supports locks via the commonly used Lock, Condition, and ReadWriteLock interfaces and via the ReentrantLock and ReentrantReadWriteLock classes.
The java.util.concurrent.atomic package provides a small toolkit of classes that support lock-free, thread-safe operations on single variables. The classes in this package extend the notion of volatile values, fields, and array elements to those that also provide an atomic conditional update so that external synchronization isn’t required. Examples of atomic variable classes include AtomicBoolean and AtomicIntegerArray.
This chapter ends our tour of Java’s Collections Framework and related Concurrency Utilities. In Chapter 12, we’ll explore Java’s classic I/O APIs: File, RandomAccessFile, streams, and writers/readers.