Concurrency

Introducing Threads

• It follows, then, that a single-threaded process is one that contains exactly one thread, whereas a multi-threaded process is one that contains one or more threads.

• A task is a single unit of work performed by a thread.


Distinguishing Thread Types

• A system thread is created by the JVM and runs in the background of the application.


Understanding Thread Concurrency

• The property of executing multiple threads and processes at the same time is referred to as concurrency.

• Operating systems use a thread scheduler to determine which threads should be currently executing.

• When a thread’s allotted time is complete but the thread has not  nished processing, a context switch occurs. A context switch is the process of storing a thread’s current state and later restoring the state of the thread to continue execution.

• Finally, a thread can interrupt or supersede another thread if it has a higher thread priority than the other thread.


Introducing Runnable


Creating a Thread


• On the exam, be careful about cases where a Thread or Runnable is created but no start() method is called. While the following code snippets will compile, none will actually execute a task on a separate processing thread. Instead, the thread that made the call will be used to execute the task, causing the thread to wait until each run() method is complete before moving on to the next line.

• In general, you should extend the Thread class only under very speci c circumstances, such as when you are creating your own priority-based thread.


Polling with Sleep

Thread.sleep()

Creating Threads with the ExecutorService
Introducing the Single-Thread Executor
• Concurrency API includes the Executors factory class that can be used to create instances of the ExecutorService object.



Shutting Down a Thread Executor


• Note that shutdownNow() attempts to stop all running tasks.

• Unfortunately, the ExecutorService interface does not implement AutoCloseable, so you cannot use a try-with-resources statement

• In such a scenario, you would need to de ne a static method that can be called anytime the user signals that they wish to exit the program. Remember that failure to shut down a thread executor after at least one thread has been created will result in the program hanging.


Submitting Tasks

Submitting Task Collections
• invokeAll() and invokeAny(). Both of these methods take a Collection object containing a list of tasks to execute.

• Both of these methods also execute synchronously. By synchronous, we mean that unlike the other methods used to submit tasks to a thread executor, these methods will wait until the results are available before returning control to the enclosing program.

• The invokeAll() method executes all tasks in a provided collection and returns a List of ordered Future objects, with one Future object corresponding to each submitted task, in the order they were in the original collection.

• The invokeAny() method executes a collection of tasks and returns the result of one of the tasks that successfully completes execution, cancelling all un nished tasks.

• While the  first task to  finish is often returned, this behavior is not guaranteed, as any completed task can be returned by this method.

• Finally, the invokeAll() method will wait inde nitely until all tasks are complete,

• while the invokeAny() method will wait inde nitely until at least one task completes


Waiting for Results

• but it does not use the Thread class directly. In part, this is the essence of the Concurrency API:



Introducing Callable

• The Callable interface was introduced as an alternative to the Runnable interface, since it allows more details to be retrieved easily from the task after it is completed.

• ExecutorService includes an overloaded version of the submit() method that takes a Callable object and returns a generic Future<T> object.


• prints 41


Waiting for All Tasks to Finish


• The method waits the speci ed time to complete all tasks, returning sooner if all tasks  nish or an InterruptedException is detected. awaitTermniation method


Scheduling Tasks

• If you are familiar with creating Cron jobs in Linux to schedule tasks, then you should know that scheduleAtFixedRate() is the closest built-in Java equivalent.


Increasing Concurrency with Pools

• Which happens when more tasks are submitted to a thread executor than available thread the task will be added to an internal queue and completed when there is an available thread.

Synchronizing Data Access

•     the unexpected result of two tasks executing at the same time is referred to as a race condition.


Protecting Data with Atomic Classes
• Atomic is the property of an operation to be carried out as a single unit of execution without any interference by another thread. 


Improving Access with Synchronized Blocks

Synchronizing Methods

Understanding the Cost of Synchronization
• Synchronization is about protecting data integrity at the cost of performance.

• Being able to identify synchronization problems, including  nding ways to improve performance in synchronized multi-threaded environments, is a valuable skill in practice.


Using Concurrent Collections
Introducing Concurrent Collections

Understanding Memory Consistency Errors
• A memory consistency error occurs when two threads have inconsistent views of what should be the same data.

• When two threads try to modify the same non-concurrent collection, the JVM may throw a ConcurrentModificationException at runtime.


Working with Concurrent Classes
• In the same way that we instantiate an ArrayList object but pass around a List reference, it is considered a good practice to instantiate a concurrent collection but pass it around using a non-concurrent interface whenever possible.



Understanding Blocking Queues

• The BlockingQueue is just like a regular Queue, except that it includes methods that will wait a speci c amount of time to complete an operation.

• Since BlockingQueue inherits all of the methods from Queue, we skip the inherited methods and present the new waiting methods in Table 7.10.



Understanding SkipList Collections
• The SkipList classes, ConcurrentSkipListSet and ConcurrentSkipListMap, are concurrent versions of their sorted counterparts, TreeSet and TreeMap, respectively


Understanding CopyOnWrite Collections


• The CopyOnWrite classes are similar to the immutable object pattern that you saw in Chapter 2, as a new underlying structure is created every time the collection is modified.

• Unlike the immutable object pattern, though, the reference to the object stays the same even while the underlying data is changed.

• The CopyOnWrite classes can use a lot of memory, since a new collection structure needs be allocated anytime the collection is modi ed. They are commonly used in multi-threaded environment situations where reads are far more common than writes.

• The CopyOnWriteArrrayList class is designed to preserve the original list on iteration,

• The ConcurrentSkipListSet class allows modifications while iterating, so it is possible that the second loop could generate an infinite loop.


Obtaining Synchronized Collections

• Concurrency API also includes methods for obtaining synchronized versions of existing non-concurrent collection objects.



• Not without synchronized block and if modified the collection exception will be thrown.
• When should you use these methods? If you know at the time of creation that your object requires synchronization


Working with Parallel Streams

• A serial stream is a stream in which the results are ordered, with only one entry being processed at a time.

• A parallel stream is a stream that is capable of processing results concurrently, using multiple threads.

• Using a parallel stream can change not only the performance of your application but also the expected results.

• By default, the number of threads available in a parallel stream is related to the number of available CPUs in your environment. In order to increase the thread count, you would need to create your own custom class.


Creating Parallel Streams
• The Stream interface includes a method isParallel() that can be used to test if the instance of a stream supports parallel processing. Some operations on streams preserve the parallel attribute, while others do not. For example, the Stream.concat(Stream s1, Stream s2) is parallel if either s1 or s2 is parallel. On the other hand, flatMap() creates a new stream that is not parallel by default, regardless of whether the underlying elements were parallel.



Processing Tasks in Parallel


• the forEach() operation on a parallel stream is equivalent to submitting multiple Runnable lambda expressions to a pooled thread executor.


Understanding Performance Improvements

• For small streams, the improvement is often limited, as there are some overhead costs to allocating and setting up the parallel processing.

• Parallel streams tend to achieve the most improvement when the number of elements in the stream is signi cantly large



Understanding Independent Operations

• By independent operations, we mean that the results of an operation on one element of a stream do not require or impact the results of another element of the stream.


• For the exam, you should remember that parallel streams can process results independently, although the order of the results cannot be deter- mined ahead of time.


Avoiding Stateful Operations

• A stateful lambda expression is one whose result depends on any state that might change dur- ing the execution of a pipeline.


Without parallel streams it would have been 1 2 3 4 5 6


Processing Parallel Reductions

• Reduction operations on parallel streams are referred to as parallel reductions. 
• The results for parallel reductions can be different from what you expect when working with serial streams.


Performing Order-Based Tasks

• Any stream operation that is based on order, including findFirst(), limit(), or skip(), may actually perform more slowly in a parallel environment.

• with parallel streams, the results of findAny() are no longer predictable.



• On the plus side, the results of ordered operations on a parallel stream will be consistent with a serial stream. For example, calling skip(5).limit(2).findFirst() will return the same result on ordered serial and parallel streams.



Combining Results with reduce()

• . Whereas with a serial stream, wolf was built one character at a time, in a parallel stream, the intermediate strings wo and lf could have been created and then combined.


• Note that these principles still apply to the identity and accumulator when using the one- or two-argument version of reduce() on parallel streams.


Combing Results with collect()


Using the One-Argument collect() Method


• The Collectors class includes two sets of methods for retrieving collectors that are both UNORDERED and CONCURRENT, Collectors.toConcurrentMap() and Collectors.groupingByConcurrent(), and therefore it is capable of performing parallel reductions eficiently


Managing Concurrent Processes

Creating a CyclicBarrier

• If you are using a thread pool, make sure that you set the number of available threads to be at least as large as your CyclicBarrier limit value.


Applying the Fork/Join Framework

• Recursion is the process by which a task calls itself to solve a problem. A recursive solution is constructed with a base case and a recursive case:



• For the exam, you should know how to implement the fork/join solution by extending one of two classes, RecursiveAction and RecursiveTask, both of which imple- ment the ForkJoinTask interface.

• As you might have guessed, the difference between RecursiveAction and RecursiveTask is analogous to the difference between Runnable and Callable, respectively, which you saw at the start of the chapter.

• The second class, RecursiveTask, is an abstract generic class that requires us to implement the compute() method, which returns the generic type, to perform the bulk of the work.

• The  first class, RecursiveAction, is an abstract class that requires us to implement the compute() method, which returns void, to perform the bulk of the work.



Working with a RecursiveTask


• For the exam, make sure that fork() is called before the current thread begins a subtask and that join() is called after it  nishes retrieving the results, in order for them to be done in parallel.

• The fork() method instructs the fork/join framework to complete the task in a separate thread, while the join() method causes the current thread to wait for the results.


Identifying Fork/Join Issues

Identifying Threading Problems

Understanding Liveness

• Liveness is the ability of an application to be able to execute in a timely manner.

• For the exam, there are three types of liveness issues with which you should be familiar: deadlock, starvation, and livelock.


Deadlock
• Deadlock occurs when two or more threads are blocked forever, each waiting on the other.

• This example is considered a deadlock because both participants are permanently blocked, waiting on resources that will never become available.


Starvation
• Starvation occurs when a single thread is perpetually denied access to a shared resource or lock.


Livelock
• Livelock occurs when two or more threads are conceptually blocked forever, although they are each still active and trying to complete their task.


Managing Race Conditions

• A race condition is an undesirable result that occurs when two tasks, which should be completed sequentially, are completed at the same time.


c