© Peter Späth and Jeff Friesen 2020
P. Späth, J. FriesenLearn Java for Android Developmenthttps://doi.org/10.1007/978-1-4842-5943-6_11

11. Exploring the Concurrency Utilities

Peter Späth1  and Jeff Friesen2
(1)
Leipzig, Sachsen, Germany
(2)
Winnipeg, MB, Canada
 

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

The low-level Threads API lets you create multithreaded applications that offer better performance and responsiveness over their single-threaded counterparts. However, there are problems:
  • 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.

To address these problems, Java 5 introduced the Concurrency Utilities , a powerful and extensible framework of high-performance threading utilities such as thread pools and blocking queues. This framework consists of the following packages:
  • 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.

Note

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

Note

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.

Note

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.

Note

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

The following example presents the Executor equivalent of the aforementioned new Thread(new RunnableTask()).start(); expression:
Executor executor = ...; //  ... represents some executor creation
executor.execute(new RunnableTask());
Although Executor is easy to use, this interface is limited in various ways:
  • 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.

Because online access can be slow, and because the user interface should remain responsive (perhaps the user might want to end the application), you offload the “obtain word entries” task to an executor that runs this task on a separate thread. The following example uses ExecutorService, Callable, and Future to accomplish this objective:
ExecutorService executor = Executors.newFixedThreadPool(4); //  some executor creation
Future<String[]> taskFuture = executor.submit(
    new Callable<String[]>() {
        @Override
        public String[] call() {
            String[] entries = ...;
            // Access online dictionaries
            // with search word and populate
            // entries with their resulting
            // entries.
            return entries;
        }
    });
// Do stuff.
String entries = taskFuture.get();

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.

Note

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.

Note

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.

Although they’re often needed to simplify code in a multithreaded environment, there are a couple of problems with thread-safe collections:
  • 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.

The Concurrency Utilities framework addresses these problems by introducing performant and highly scalable collections-oriented types, which are stored in the java.util.concurrent package. Its collections-oriented classes return weakly consistent iterators, which are iterators that have the following properties:
  • 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.

The following list offers a short sample of concurrency-oriented collection types that you’ll find in the java.util.concurrent package:
  • 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

The Lock interface offers more extensive locking operations than can be obtained via the locks associated with monitors. For example, you can immediately back out of a lock-acquisition attempt when a lock isn’t available. This interface declares the following methods:
  • 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.

With this increased flexibility comes additional responsibility. The absence of block-structured locking removes the automatic release of locks that occurs with synchronized methods and statements. As a result, you should typically employ the following idiom for lock acquisition and release:
Lock l = ...; // ... is a placeholder for code that obtains the lock
l.lock();
try  {
  // access the resource protected by this lock
} catch (Exception ex)  {
  // ...
} finally  {
   l.unlock();
}

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.

Note

A Condition instance is intrinsically bound to a lock. To obtain a Condition instance for a particular Lock instance, use Lock’s newCondition() method.

Condition declares the following methods:
  • 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.)

ReadWriteLock declares the following methods:
  • 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.

You initialize a ReentrantReadWriteLock instance by invoking either of the following constructors:
  • 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.

After instantiating this class, you would invoke the following methods to obtain the read and write locks:
  • ReentrantReadWriteLock.ReadLock readLock(): Returns the lock used for reading

  • ReentrantReadWriteLock.WriteLock writeLock(): Returns the lock used for writing

Each of the nested ReadLock and WriteLock classes implements the Lock interface and declares its own methods. Furthermore, ReentrantReadWriteLock declares additional methods such as the following pair:
  • 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.

Some of the classes located in this package are described as follows:
  • 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

In
public class ID {
   private static long nextID = 0;
   public static long getNextID() {
      return nextID++;
   }
}
we define a small utility class for returning unique long integer identifiers via the getNextID() class method. Because this method isn’t synchronized, multiple threads could obtain the same identifier. Listing 11-1 fixes this problem by including reserved word synchronized in the method header.
public class ID {
   private static long nextID = 0;
   public static synchronized long getNextID() {
      return nextID++;
   }
}
Listing 11-1

Returning Unique Identifiers in a Thread-Safe Manner via synchronized

Although synchronized is appropriate for this example, excessive use of this reserved word in more complex classes can lead to deadlock, starvation, or other problems. Listing 11-2 shows you how to avoid these assaults on a concurrent application’s liveness (the ability to execute in a timely manner) by replacing synchronized with an atomic variable.
import java.util.concurrent.atomic.AtomicLong;
public class ID {
   private static AtomicLong nextID = new AtomicLong(0);
   public static long getNextID() {
      return nextID.getAndIncrement();
   }
}
Listing 11-2

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.

Exercises
The following exercises are designed to test your understanding of Chapter 11’s content:
  1. 1.

    Define Concurrency Utilities.

     
  2. 2.

    Identify the packages in which Concurrency Utilities types are stored.

     
  3. 3.

    Define task.

     
  4. 4.

    Define executor.

     
  5. 5.

    Identify the Executor interface’s limitations.

     
  6. 6.

    What differences exist between Runnable’s run() method and Callable’s call() method?

     
  7. 7.

    Define future.

     
  8. 8.

    Describe the Executors class’s newFixedThreadPool() method.

     
  9. 9.

    Define synchronizer.

     
  10. 10.

    What concurrency-oriented extensions to the Collections Framework are provided by the Concurrency Utilities?

     
  11. 11.

    Define lock.

     
  12. 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. 13.

    How do you obtain a Condition instance for use with a particular Lock instance?

     
  14. 14.

    Define atomic variable.

     
  15. 15.

    What does the AtomicIntegerArray class describe?

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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.24.60