APPENDIX A

image

Answers to Exercises

Each of Chapters 1 through 8 closes with an “Exercises” section that tests your understanding of the chapter’s material. The answers to those exercises are presented in this appendix.

Chapter 1: Threads and Runnables

  1. A thread is an independent path of execution through an application’s code.
  2. A runnable is a code sequence encapsulated into an object whose class implements the Runnable interface.
  3. The Thread class provides a consistent interface to the underlying operating system’s threading architecture. The Runnable interface supplies the code to be executed by the thread that’s associated with a Thread object.
  4. The two ways to create a Runnable object are to instantiate an anonymous class that implements the Runnable interface and to use a lambda expression.
  5. The two ways to connect a runnable to a Thread object are to pass the runnable to a Thread constructor that accepts a runnable argument and to subclass Thread and override its void run() method when the constructor doesn’t accept a runnable argument. Thread implements the Runnable interface, which makes Thread objects runnables as well.
  6. The five kinds of Thread state are a name, an indication of whether the thread is alive or dead, the execution state of the thread (is it runnable?), the thread’s priority, and an indication of whether the thread is daemon or nondaemon.
  7. The answer is false: a default thread name starts with the Thread- prefix.
  8. You give a thread a nondefault name by calling a Thread constructor that accepts a thread name or by calling Thread’s void setName(String name) method.
  9. You determine if a thread is alive or dead by calling Thread’s boolean isAlive() method.
  10. The Thread.State enum’s constants are NEW (a thread that has not yet started is in this state), RUNNABLE (a thread executing in the Java virtual machine [JVM] is in this state), BLOCKED (a thread that is blocked waiting for a monitor lock is in this state), WAITING (a thread that is waiting indefinitely for another thread to perform a particular action is in this state), TIMED_WAITING (a thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state), and TERMINATED (a thread that has exited is in this state).
  11. You obtain the current thread execution state by calling Thread’s Thread.State getState() method.
  12. Priority is thread-relative importance.
  13. Using setPriority() can impact an application’s portability across operating systems because different schedulers can handle a priority change in different ways.
  14. The range of values that you can pass to Thread’s void setPriority(int priority) method are Thread.MIN_PRIORITY to Thread.MAX_PRIORITY.
  15. The answer is true: a daemon thread dies automatically when the application’s last nondaemon thread dies so that the application can terminate.
  16. Thread’s void start() method throws IllegalThreadStateException when called on a Thread object whose thread is running or has died.
  17. You would stop an unending application on Windows by pressing the Ctrl and C keys simultaneously.
  18. The methods that form Thread’s interruption mechanism are void interrupt(), static boolean interrupted(), and boolean isInterrupted().
  19. The answer is false: the boolean isInterrupted() method doesn’t clear the interrupted status of this thread. The interrupted status is unaffected.
  20. A thread throws InterruptedException when it’s interrupted.
  21. A busy loop is a loop of statements designed to waste some time.
  22. Thread’s methods that let a thread wait for another thread to die are void join(), void join(long millis), and void join(long millis, int nanos).
  23. Thread’s methods that let a thread sleep are void sleep(long millis) and void sleep(long millis, int nanos).
  24. Listing A-1 presents the IntSleep application that was called for in Chapter 1.

Chapter 2: Synchronization

  1. The three problems with threads are race conditions, data races, and cached variables.
  2. The answer is false: when the correctness of a computation depends on the relative timing or interleaving of multiple threads by the scheduler, you have a race condition.
  3. Synchronization is a JVM feature that ensures that two or more concurrent threads don’t simultaneously execute a critical section.
  4. The two properties of synchronization are mutual exclusion and visibility.
  5. Synchronization is implemented in terms of monitors, which are concurrency constructs for controlling access to critical sections, which must execute indivisibly. Each Java object is associated with a monitor, which a thread can lock or unlock acquiring and releasing the monitor’s lock token.
  6. The answer is true: a thread that has acquired a lock doesn’t release this lock when it calls one of Thread’s sleep() methods.
  7. You specify a synchronized method by including the keyword synchronized in the method header.
  8. You specify a synchronized block by specifying the syntax synchronized(object) {}.
  9. Liveness refers to something beneficial happening eventually.
  10. The three liveness challenges are deadlock, livelock, and starvation (also known as indefinite postponement).
  11. The volatile keyword differs from synchronized in that volatile deals with visibility only, whereas synchronized deals with mutual exclusion and visibility.
  12. The answer is true: Java also lets you safely access a final field without the need for synchronization.
  13. The thread problems with the CheckingAccount class are the check-then-act race condition in the withdraw() method between if (amount <= balance) and balance -= amount; (which results in more money being withdrawn than is available for withdrawal) and the potentially cached balance field. The balance field can be cached on multiprocessor/multicore systems and the cached copy used by the withdrawal thread might not contain the initial balance set in the constructor by the default main thread.
  14. Listing A-2 presents the CheckingAccount application that was called for in Chapter 2.

This application uses volatile to deal with potential cache problems and synchronized to deal with the need for mutual exclusion.

Chapter 3: Waiting and Notification

  1. A condition is a prerequisite for continued execution.
  2. The API that supports conditions consists of Object’s three wait() methods, one notify() method, and one notifyAll() method. The wait() methods wait for a condition to exist; the notify() and notifyAll() methods notify the waiting thread when the condition exists.
  3. The answer is true: the wait() methods are interruptible.
  4. You would call the notifyAll() method to wake up all threads that are waiting on an object’s monitor.
  5. The answer is false: a thread that has acquired a lock releases this lock when it calls one of Object’s wait() methods.
  6. A condition queue is a data structure that stores threads waiting for a condition to exist. The waiting threads are known as the wait set.
  7. When you call any of the API’s methods outside of a synchronized context, IllegalMonitorStateException is thrown.
  8. A spurious wakeup is a thread waking up without being notified, interrupted, or timing out.
  9. You should call a wait() method in a loop context to ensure liveness and safety.
  10. Listing A-3 presents the Await application that was called for in Chapter 3.

Chapter 4: Additional Thread Capabilities

  1. A thread group is a set of threads. It’s represented by the ThreadGroup class.
  2. You might use a thread group to perform a common operation on its threads, to simplify thread management.
  3. You should avoid using thread groups because the most useful ThreadGroup methods have been deprecated and because of the “time of check to time of use” race condition between obtaining a count of active threads and enumerating those threads.
  4. You should be aware of thread groups because of ThreadGroup’s contribution in handling exceptions that are thrown while a thread is executing.
  5. A thread-local variable is a variable that provides a separate storage slot to each thread that accesses the variable. It’s represented by the ThreadLocal class.
  6. The answer is true: if an entry doesn’t exist in the calling thread’s storage slot when the thread calls get(), this method calls initialValue().
  7. You would pass a value from a parent thread to a child thread by working with the InheritableThreadLocal class.
  8. The classes that form the Timer Framework are Timer and TimerTask.
  9. The answer is false: Timer() creates a new timer whose task-execution thread runs as a nondaemon thread.
  10. In fixed-delay execution, each execution is scheduled relative to the actual execution time of the previous execution. When an execution is delayed for any reason (such as garbage collection), subsequent executions are also delayed.
  11. You call the schedule() methods to schedule a task for fixed-delay execution.
  12. In fixed-rate execution, each execution is scheduled relative to the scheduled execution time of the initial execution. When an execution is delayed for any reason (such as garbage collection), two or more executions will occur in rapid succession to “catch up.”
  13. The difference between Timer’s cancel() method and TimerTask’s cancel() method is as follows: Timer’s cancel() method terminates the timer, discarding any currently scheduled timer tasks. In contrast, TimerTask’s cancel() method cancels the invoking timer task only.
  14. Listing A-4 presents the BackAndForth application that was called for in Chapter 4.

Chapter 5: Concurrency Utilities and Executors

  1. The concurrency utilities are a framework of classes and interfaces that overcome problems with Java’s low-level thread capabilities. Specifically, low-level concurrency primitives such as synchronized and wait()/notify() are often hard to use correctly, too much reliance on the synchronized primitive can lead to performance issues, which affect an application’s scalability, and higher-level constructs such as thread pools and semaphores aren’t included with Java’s low-level thread capabilities.
  2. The packages in which the concurrency utilities types are stored are java.util.concurrent, java.util.concurrent.atomic, and java.util.concurrent.locks.
  3. A task is an object whose class implements the Runnable interface (a runnable task) or the Callable interface (a callable task).
  4. An executor is an object whose class directly or indirectly implements the Executor interface, which decouples task submission from task-execution mechanics.
  5. The Executor interface focuses exclusively on Runnable, which means that there’s no convenient way for a runnable task to return a value to its caller (because Runnable’s run() method doesn’t return a value); Executor doesn’t provide a way to track the progress of executing runnable tasks, cancel an executing runnable task, or determine when the runnable task finishes execution; Executor cannot execute a collection of runnable tasks; and Executor doesn’t provide a way for an application to shut down an executor (much less to properly shut down an executor).
  6. Executor’s limitations are overcome by providing the ExecutorService interface.
  7. The differences existing between Runnable’s run() method and Callable’s call() method are as follows: run() cannot return a value, whereas call() can return a value; and run() cannot throw checked exceptions, whereas call() can throw checked exceptions.
  8. The answer is false: you can throw checked and unchecked exceptions from Callable’s call() method but can only throw unchecked exceptions from Runnable’s run() method.
  9. A future is an object whose class implements the Future interface. It represents an asynchronous computation and provides methods for canceling a task, for returning a task’s value, and for determining whether or not the task has finished.
  10. The Executors class’s newFixedThreadPool() method 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. If any thread terminates because of a failure during execution before the executor shuts down, a new thread will take its place when needed to execute subsequent tasks. The threads in the pool will exist until the executor is explicitly shut down.
  11. Listing A-5 presents the CountingThreads application that was called for in Chapter 5.
  12. Listing A-6 presents the CountingThreads application with custom-named threads that was called for in Chapter 5.

Chapter 6: Synchronizers

  1. A synchronizer is a class that facilitates a common form of synchronization.
  2. 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”.
  3. When CountDownLatch’s void countDown() method is called and the count reaches zero, all waiting threads are released.
  4. 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.
  5. The answer is false: CyclicBarrier’s int await() method throws BrokenBarrierException when the barrier is reset while any thread is waiting or when the barrier is broken when await() is invoked.
  6. 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.
  7. Exchanger’s V exchange(V x) method waits for another thread to arrive at this exchange point (unless the calling thread is interrupted), and then transfers the given object to it, receiving the other thread’s object in return.
  8. 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.
  9. The two kinds of semaphores are counting semaphores (the current values can be incremented past 1) and binary semaphores or mutexs (the current values can be only 0 or 1).
  10. A phaser is a more flexible cyclic barrier. Like a cyclic barrier, a phaser lets a group of threads wait on a barrier; these threads continue after the last thread arrives. A phaser also offers the equivalent of a barrier action. Unlike a cyclic barrier, which coordinates a fixed number of threads, a phaser can coordinate a variable number of threads, which can register at any time. To implement this capability, a phaser uses phases (current states) and phase numbers (current state identifiers).
  11. Phaser’s int register() method returns the phase number to classify the arrival. If this value is negative, this phaser has terminated, in which case registration has no effect. This number is known as the arrival phase number.
  12. Listing A-7 presents the PC application that was called for in Chapter 6.

Chapter 7: The Locking Framework

  1. A lock is an instance of a class that implements the Lock interface, which provides more extensive locking operations than can be achieved via the synchronized reserved word. Lock also supports a wait/notification mechanism through associated Condition objects.
  2. The biggest advantage that Lock objects hold over the intrinsic locks that are obtained when threads enter critical sections (controlled via the synchronized reserved word) is their ability to back out of an attempt to acquire a lock.
  3. The answer is true: ReentrantLock’s unlock() method throws IllegalMonitorStateException when the calling thread doesn’t hold the lock.
  4. You obtain a Condition instance for use with a particular Lock instance by invoking Lock’s Condition newCondition() method.
  5. The answer is false: ReentrantReadWriteLock() creates an instance of ReentrantReadWriteLock without a fair ordering policy.
  6. Introduced by JDK 8, StampedLock is a capability-based lock with three modes for controlling read/write access. It differentiates between exclusive and nonexclusive locks in a manner that’s similar to ReentrantReadWriteLock, but also allows for optimistic reads, which ReentrantReadWriteLock doesn’t support.
  7. The purpose of LockSupport is to provide basic thread-blocking primitives for creating locks and other synchronization classes.
  8. Listing A-8 presents the ID class that was called for in Chapter 7.

Chapter 8: Additional Concurrency Utilities

  1. The two problems with thread-safe collections are the possibility of thrown ConcurrentModificationException objects and poor performance. 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 ConcurrentModificationException will be thrown. Also, performance suffers when synchronized collections are accessed frequently from multiple threads.
  2. A concurrent collection is a concurrency performant and highly-scalable collection-oriented type that is stored in the java.util.concurrent package.
  3. A weakly-consistent iterator is an iterator with 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 iteration of a collection, regardless of changes made to the collection during iteration.
  4. 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.
  5. ConcurrentMap is a subinterface of java.util.Map that declares additional indivisible putIfAbsent(), remove(), and replace() methods.
  6. ArrayBlockingQueue is a bounded blocking queue backed by an array. LinkedBlockingQueue is an optionally-bounded blocking queue based on linked nodes.
  7. The answer is true: the concurrency-oriented collection types are part of the Collections Framework.
  8. ConcurrentHashMap behaves like HashMap but has been designed to work in multithreaded contexts without the need for explicit synchronization.
  9. Using ConcurrentHashMap, you would call its putIfAbsent() method to check if a map contains a specific value and, when this value is absent, put this value into the map without relying on external synchronization.
  10. An atomic variable is an instance of a class that encapsulates a single variable and supports lock-free, thread-safe operations on that variable, for example, AtomicInteger.
  11. The AtomicIntegerArray class describes an int array whose elements may be updated atomically.
  12. The answer is false: volatile doesn’t support atomic read-modify-write sequences.
  13. The compare-and-swap instruction is responsible for the performance gains offered by the concurrency utilities.
  14. The Fork/Join Framework consists of a special executor service and thread pool. The executor service makes a task available to the framework, and this task is broken down into smaller tasks that are forked (executed by different threads) from the pool. A task waits until it’s joined (its subtasks finish).
  15. The main types that comprise the Fork/Join Framework are the java.util.concurrent package’s ForkJoinPool, ForkJoinTask, ForkJoinWorkerThread, RecursiveAction, RecursiveTask, and CountedCompleter classes.
  16. To accomplish meaningful work via RecursiveAction, you would override its void compute() method.
  17. A completion service is an implementation of the CompletionService<V> interface that decouples the production of new asynchronous tasks (a producer) from the consumption of the results of completed tasks (a consumer). V is the type of a task result.
  18. You use a completion service as follows: Submit a task for execution (via a worker thread) by calling one of CompletionService<V>’s submit() methods. Each method returns a Future<V> instance that represents the pending completion of the task. You can then call a poll() method to poll for the task’s completion or call the blocking take() method. A consumer takes a completed task by calling the take() method. This method blocks until a task has completed. It then returns a Future<V> object that represents the completed task. You would call Future<V>’s get() method to obtain this result.
  19. You execute tasks via a completion service by working with the ExecutorCompletionService<V> class, which implements CompletionService<V>, and which supports task execution via a provided executor.
  20. The atomic variable equivalent of int total = ++counter; is as follows:
    AtomicInteger counter = new AtomicInteger(0);
    int total = counter.incrementAndGet();

    The atomic variable equivalent of int total = counter--; is as follows:

    AtomicInteger counter = new AtomicInteger(0);
    int total = counter.getAndDecrement();
..................Content has been hidden....................

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