PolarSPARC |
Java 8 CompletableFuture :: Part 4 (Final)
Bhaskar S | 11/10/2018 |
In Part 1, Part 2 and Part 3 of this series, we demonstrated some of the capabilities and nuances in CompletableFuture. In this final part, we will wrap-up the series by covering the following:
Case. 1 :: Demonstrate a simple pipeline of tasks using some of the method(s) covered thus far in this series from CompletableFuture
Case. 2 :: Performance characteristics of a hypothetical payment processing use-case using CompletableFuture
Let us get started without much further ado.
Case. 1
There are scenarios where one could have a task consume input from two sources of data and once the processing is completed, trigger more than one child task(s). The following diagram illustrates a simple pipeline of tasks tackling this exact case:
Note that we used the word Supply for the method thenSupply the word Combine for the method thenCombine and so on.
Executing the program from Listing.11 will generate the following output:
[1] [pool-1-thread-1] Random number is 47 [2] [pool-1-thread-2] Random number is 28 [4] [pool-1-thread-4] Random seed is 2 [3] [pool-1-thread-3] Transformed number is 19 [5] [pool-1-thread-2] Final number is 38 [6] [pool-1-thread-4] Done !!!
Re-running the program from Listing.11 a few times will generate the following output:
[1] [pool-1-thread-1] Random number is 91 [2] [pool-1-thread-2] Random number is 7 EXCEPTION:: java.lang.RuntimeException: n1 = 91, n2 = 7 => Invalid combination
Again, re-running the program from Listing.11 a few times will generate the following output:
[1] [pool-1-thread-1] Random number is 818 [2] [pool-1-thread-2] Random number is 69 [4] [pool-1-thread-4] Random seed is 4 [6] [pool-1-thread-4] Done !!! [5] [pool-1-thread-1] Final number is 236 [3] [pool-1-thread-3] Transformed number is 59
The following are some of the concepts in the context of the code in Listing.11:
TimeUnit :: An enum type that represents unit of time at different levels of granularity and provides utility methods to convert between time units
get(long, TimeUnit) :: This method waits upto the specified unit of time and returns the results if the CompletableFuture completes before the time expires or throws a TimeoutException exception
thenRun(Runnable) :: This method is defined on the interface CompletionStage and accepts an instance of type Runnable, which gets executed using the default fork-join thread pool when the previous stage (or task) completes. The method returns an instance of CompletionStage
Case. 2
In this example, we will measure the performance characteristics of a simple hypothetical payments processing use-case using different approaches, such as Threads, Futures, etc. For the payments processing, each payment instruction encapsulates a transaction between a buyer and a seller for a given amount. Processing each payment instruction involves the following three steps:
Step. 1 :: Lookup the account number for the buyer
Step. 2 :: Lookup the account number for the seller
Step. 3 :: Debit the buyer for the specified amount only if buyer balance is greater than the specified amount and credit the seller for the specified amount. Else generate an exception
The following class Instruction represents a payment instruction:
The following class Account class represents an account (buyer or seller):
The following class Settlement wraps a payment settlement:
The following class Utility encapsulates some utility functions:
The following class ClientServices encapsulates the client (buyer or seller) to account mappings:
The following class AccountServices encapsulates payment processing by providing a simple facade method for debit and credit operation:
And finally, the following class PaymentsProcessor is the driver for measuring the performance characteristics of the various approaches to payments processing:
Executing the above program PaymentsProcessor will generate the following output:
1541882058692 [main] <PaymentsProcessor:main> ---> Ready to start 1541882058719 [main] <PaymentsProcessor:approachOne> =====> Ready to start 1541882059378 [main] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10006, Amount - 5.00, Buyer balance - 4995.00, Seller balance - 10005.00 1541882060031 [main] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10010, Amount - 5.00, Buyer balance - 12495.00, Seller balance - 10005.00 1541882060685 [main] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10010, Amount - 5.00, Buyer balance - 4990.00, Seller balance - 10010.00 1541882061338 [main] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12490.00, Seller balance - 10010.00 1541882061991 [main] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10006, Amount - 5.00, Buyer balance - 7495.00, Seller balance - 10015.00 1541882062645 [main] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10010, Amount - 5.00, Buyer balance - 7490.00, Seller balance - 10015.00 1541882063298 [main] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10009, Amount - 5.00, Buyer balance - 4985.00, Seller balance - 7505.00 1541882063951 [main] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12485.00, Seller balance - 10020.00 1541882064604 [main] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10008, Amount - 5.00, Buyer balance - 12480.00, Seller balance - 15005.00 1541882065257 [main] <AccountServices:settle> Buyer account - A-10004, Seller account - A-10007, Amount - 5.00, Buyer balance - 14995.00, Seller balance - 5005.00 1541882065259 [main] <PaymentsProcessor:approachOne> =====> Average time - 653 ms 1541882065261 [main] <PaymentsProcessor:approachTwo> =====> Ready to start 1541882065770 [thread-3] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10006, Amount - 5.00, Buyer balance - 4995.00, Seller balance - 10005.00 1541882066274 [thread-3] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10010, Amount - 5.00, Buyer balance - 12495.00, Seller balance - 10005.00 1541882066778 [thread-3] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10010, Amount - 5.00, Buyer balance - 4990.00, Seller balance - 10010.00 1541882067281 [thread-3] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12490.00, Seller balance - 10010.00 1541882067784 [thread-3] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10006, Amount - 5.00, Buyer balance - 7495.00, Seller balance - 10015.00 1541882068288 [thread-3] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10010, Amount - 5.00, Buyer balance - 7490.00, Seller balance - 10015.00 1541882068791 [thread-3] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10009, Amount - 5.00, Buyer balance - 4985.00, Seller balance - 7505.00 1541882069295 [thread-3] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12485.00, Seller balance - 10020.00 1541882069798 [thread-3] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10008, Amount - 5.00, Buyer balance - 12480.00, Seller balance - 15005.00 1541882070301 [thread-3] <AccountServices:settle> Buyer account - A-10004, Seller account - A-10007, Amount - 5.00, Buyer balance - 14995.00, Seller balance - 5005.00 1541882070303 [main] <PaymentsProcessor:approachTwo> =====> Average time - 504 ms 1541882070313 [main] <PaymentsProcessor:approachThree> =====> Ready to start 1541882070821 [pool-1-thread-3] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10006, Amount - 5.00, Buyer balance - 4995.00, Seller balance - 10005.00 1541882071324 [pool-1-thread-1] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10010, Amount - 5.00, Buyer balance - 12495.00, Seller balance - 10005.00 1541882071827 [pool-1-thread-2] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10010, Amount - 5.00, Buyer balance - 4990.00, Seller balance - 10010.00 1541882072330 [pool-1-thread-3] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12490.00, Seller balance - 10010.00 1541882072832 [pool-1-thread-4] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10006, Amount - 5.00, Buyer balance - 7495.00, Seller balance - 10015.00 1541882073335 [pool-1-thread-1] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10010, Amount - 5.00, Buyer balance - 7490.00, Seller balance - 10015.00 1541882073837 [pool-1-thread-2] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10009, Amount - 5.00, Buyer balance - 4985.00, Seller balance - 7505.00 1541882074339 [pool-1-thread-3] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12485.00, Seller balance - 10020.00 1541882074842 [pool-1-thread-4] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10008, Amount - 5.00, Buyer balance - 12480.00, Seller balance - 15005.00 1541882075344 [pool-1-thread-2] <AccountServices:settle> Buyer account - A-10004, Seller account - A-10007, Amount - 5.00, Buyer balance - 14995.00, Seller balance - 5005.00 1541882075346 [main] <PaymentsProcessor:approachThree> =====> Average time - 503 ms 1541882075348 [main] <PaymentsProcessor:approachFour> =====> Ready to start 1541882076002 [pool-2-thread-1] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10006, Amount - 5.00, Buyer balance - 4995.00, Seller balance - 10005.00 1541882076004 [pool-2-thread-4] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12495.00, Seller balance - 10010.00 1541882076005 [pool-2-thread-3] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10010, Amount - 5.00, Buyer balance - 4990.00, Seller balance - 10005.00 1541882076006 [pool-2-thread-2] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10010, Amount - 5.00, Buyer balance - 12490.00, Seller balance - 10010.00 1541882076654 [pool-2-thread-1] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10006, Amount - 5.00, Buyer balance - 7495.00, Seller balance - 10015.00 1541882076656 [pool-2-thread-4] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10010, Amount - 5.00, Buyer balance - 7490.00, Seller balance - 10015.00 1541882076657 [pool-2-thread-3] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10009, Amount - 5.00, Buyer balance - 4985.00, Seller balance - 7505.00 1541882076659 [pool-2-thread-2] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12485.00, Seller balance - 10020.00 1541882077306 [pool-2-thread-1] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10008, Amount - 5.00, Buyer balance - 12480.00, Seller balance - 15005.00 1541882077308 [pool-2-thread-4] <AccountServices:settle> Buyer account - A-10004, Seller account - A-10007, Amount - 5.00, Buyer balance - 14995.00, Seller balance - 5005.00 1541882077309 [main] <PaymentsProcessor:approachFour> =====> Average time - 195 ms 1541882077310 [main] <PaymentsProcessor:approachFive> =====> Ready to start 1541882077827 [ForkJoinPool.commonPool-worker-7] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10006, Amount - 5.00, Buyer balance - 4995.00, Seller balance - 10005.00 1541882077829 [ForkJoinPool.commonPool-worker-5] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10010, Amount - 5.00, Buyer balance - 12495.00, Seller balance - 10005.00 1541882077975 [ForkJoinPool.commonPool-worker-9] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10010, Amount - 5.00, Buyer balance - 4990.00, Seller balance - 10010.00 1541882077976 [ForkJoinPool.commonPool-worker-3] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12490.00, Seller balance - 10010.00 1541882078126 [ForkJoinPool.commonPool-worker-11] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10006, Amount - 5.00, Buyer balance - 7495.00, Seller balance - 10015.00 1541882078127 [ForkJoinPool.commonPool-worker-13] <AccountServices:settle> Buyer account - A-10005, Seller account - A-10010, Amount - 5.00, Buyer balance - 7490.00, Seller balance - 10015.00 1541882078276 [ForkJoinPool.commonPool-worker-5] <AccountServices:settle> Buyer account - A-10003, Seller account - A-10009, Amount - 5.00, Buyer balance - 4985.00, Seller balance - 7505.00 1541882078277 [ForkJoinPool.commonPool-worker-7] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10006, Amount - 5.00, Buyer balance - 12485.00, Seller balance - 10020.00 1541882078426 [ForkJoinPool.commonPool-worker-3] <AccountServices:settle> Buyer account - A-10002, Seller account - A-10008, Amount - 5.00, Buyer balance - 12480.00, Seller balance - 15005.00 1541882078427 [ForkJoinPool.commonPool-worker-9] <AccountServices:settle> Buyer account - A-10004, Seller account - A-10007, Amount - 5.00, Buyer balance - 14995.00, Seller balance - 5005.00 1541882078429 [main] <PaymentsProcessor:approachFive> =====> Average time - 111 ms 1541882078430 [main] <PaymentsProcessor:main> ---> Done
Notice from the listing ClientServices above, we introduce a delay of 150 ms to simulate a real-world service call to look-up an account for a given client. Similarly, from the listing AccountServices above, we introduce a delay of 350 ms to simulate a real-world service call to settle a payment between two clients (buyer and seller).
The following are some of the observations from the Output.21 above:
approachOne :: Processes each payment sequentially one after the other. The average time comes to about 653 ms
approachTwo :: Processes each payment using 3 Threads - 2 for the account lookup and 1 for the payment settlement. The average time comes to about 504 ms. Decent improvement compared to approachOne
approachThree :: Processes each payment using 3 Futures - 2 for the account lookup and 1 for the payment settlement. The average time comes to about 503 ms. Not much of an improvement compared to approachTwo
approachFour :: Processes each payment using a Future that does both the account lookups as well as the payment settlement. The average time comes to about 195 ms. This is a vast improvement compared to approachTwo. The tests are being run on different cores and each of the underlying Threads are running in parallel
approachFive :: Processes each payment using a pipeline of tasks using CompletableFutures. The average time comes to about 111 ms. This is because the tests are being run on multiple cores in parallel. This is a remarkable performance compared to approachTwo and a great improvement over approachFour
One needs to realize that CompletableFuture is not a silver bullet for every situation. One needs to really understand the use-case at hand to apply the right tools for the problem.
With that we conclude this series on CompletableFuture.