Java Reference
In-Depth Information
plier
—in this case, we use a lambdas with empty parameter lists to invoke
startFibonacci(45)
(line 39) and
startFibonacci(44)
(line 41). The compiler infers that
supplyAsync
returns a
CompletableFuture<TimeData>
because method
startFibonacci
returns type
TimeData
. Class
CompletableFuture
also provides
static
method
runAsync
to
execute an asynchronous task that does not return a result—this method receives a
Runnable
.
Getting the Asynchronous Calculations' Results
Class
CompletableFuture
implements interface
Future
, so we can obtain the asynchronous
tasks' results by calling
Future
method
get
(lines 44-45). These are
blocking
calls—they
cause the
main
thread to
wait
until the asynchronous tasks complete and return their results.
In our case, the results are
TimeData
objects. Once both tasks return, lines 46-47 pass both
TimeData
objects to method
calculateTime
(lines 91-104) to get the total calculation
time in seconds. Then, lines 48-49 display the total calculation time for the asynchronous
Fibonacci calculations. Finally, lines 52-55 calculate and display the percentage difference
in execution time for the synchronous and asynchronous calculations.
Program Outputs
On our dual-core computer, the synchronous calculations took a total of 9.506 seconds.
Though the individual asynchronous calculations took approximately the same amount of
time as the corresponding synchronous calculations, the total time for the asynchronous
calculations was only 5.911 seconds, because the two calculations were actually performed
in parallel
. As you can see in the output, the synchronous calculations took 161% more time
to complete, so asynchronous execution provided a significant performance improvement.
Java's concurrency APIs include the fork/join framework, which helps programmers par-
allelize algorithms. The framework is beyond the scope of this topic. Experts tell us that
most Java programmers will nevertheless benefit by the fork/join framework's use “behind
the scenes” in the Java API and other third party libraries. For example, the parallel capa-
bilities of Java SE 8 streams are implemented using this framework.
The fork/join framework is particularly well suited to divide-and-conquer-style algo-
rithms, such as the merge sort that we implemented in Section 19.8. Recall that the recur-
sive merge-sort algorithm sorts an array by
splitting
it into two equal-sized subarrays,
sorting
each subarray, then
merging
them into one larger array. Each subarray is sorted by
performing the same algorithm on the subarray. For algorithms like merge sort, the fork/
join framework can be used to create concurrent tasks so that they can be distributed across
multiple processors and be truly performed in parallel—the details of assigning the tasks
to different processors are handled for you by the framework.
In this chapter, we presented Java's concurrency capabilities for enhancing application per-
formance on multi-core systems. You learned the differences between concurrent and par-
allel execution. We discussed that Java makes concurrency available to you through
multithreading. You also learned that the JVM itself creates threads to run a program, and
that it also can create threads to perform housekeeping tasks such as garbage collection.