Reactive Programming provides a development model to tame the asynchronous beast.
But first… what is Reactive Programming and why do we want to use it?
In this article, we start our journey into the Reactive Programming World in Java by presenting motivations for Reactive Programming.
Two ways to improve an application’s performance
Generally, there are 2 ways for improving applications’ performance:
- use more threads and more hardware resources
- use each thread more efficiently
Why is multithreading not always efficient?
Usually, Java developers tend to write applications in a blocking way – it is fine until there is a performance bottleneck.
As soon as there is I/O operation (like a network call, or a database request), resources are wasted because threads are in idle state (waiting for data).
Additional threads and more cores can be an easy solution, but it has serious weakness:
- those additional threads are still running similar blocking code
- growing cost of infrastructure (more cores)
- a new threading in java is expensive
- each thread occupies some space in the RAM (https://www.baeldung.com/jvm-configure-stack-sizes)
- scaling in resource utilization can introduce contention and concurrency problems
How to use each thread more efficiently?
The key word is: asynchronous.
But first: what is synchronous programming?
Code is run sequentially from the top to the bottom and
Its execution is blocked on various long running IO operations (networking, database).
So what does asynchronous mean?
Synchronous – We know when it will happen.
Asynchronous – We do not know when it will happen.
Asynchronous just means the calling thread doesn’t sit and wait for the response, nor does the asynchronous activity happen in the calling thread.
Whenever the operation that needs to happen asynchronously does not require the CPU to do work, that operation can be done without spawning another thread. For example, if the async operation is I/O, the CPU does not have to wait for the I/O to complete. It just needs to start the operation, and can then move on to other work while the I/O hardware (disk controller, network interface, etc.) does the I/O work. The hardware lets the CPU know when it’s finished by interrupting the CPU, and the OS then delivers the event to your application.
Asynchronicity allows for non-blocking execution but is hard without non-blocking IO.
Note: it does not require a multithreading (even in Java) – thing about callbacks.
Synchronous vs Asynchronous vs Threaded
Let’s make some visualizations.
There are 3 tasks to be done: blue, orange and green.
On the top, there is the synchronous model.
The first, blue task is started, but there are some IO operations and the thread is waiting for a result of this IO operation. When the blue task is finished, then the orange task can start, and after the orange task completion, the last one (green) can be processed.
Please notice that IO operation can take almost all the time of execution – HTTP requests, fetching from DB, etc … IO operations take the most time of execution.
By writing asynchronous, non-blocking code, you let the execution switch to another active task and later comes back to the current process when the asynchronous IO operation has finished.
There can also be threaded models for both (synchronous and asynchronous).
An execution time for a synchronous threaded model can be widely reduced (in comparison to single-threaded model) BUT resource utilization is growing.
The most efficient approach seems to be an asynchronous threaded model.
Concurrent & Parallel
To complete our handbook, we need to take a look at concurrent vs parallel execution.
In parallel executions, tasks are running at the same time on different cores (multiple cores are needed).
In concurrent execution, many tasks are taking a slice of CPU for a small amount of time (it does not require multiple cores). Execution just seems to run simultaneously. Even programs running in single CPU setup can be concurrent.
The essence of differentiation has been great visualized in:
How to write asynchronous code on the JVM?
We know right now that asynchronous is awesome and let’s try to use that.
How can we produce asynchronous code on the JVM?
Java offers two models of asynchronous programming:
- asynchronous method takes an extra callback parameter(a lambda) that gets called when the result is available
- Introduced in Java 1.5 (2005)
- no notify on completion
- no chaining
- no exception handling
- Java 8 (2015)
- Future + missing features
Are the original Java’s techniques good enough?
Not for every use case, both approaches have their own limitations.
Callbacks are hard to compose together, quickly leading to code that is difficult to read and maintain (known as Callback Hell).
And also the (Completable)Future API is quite limited.
Taming the asynchronous beast – Reactive Programming
Let’s try to tame the asynchronous beast…
Here Reactive Programming comes to rescue.
What is Reactive Programming?
Let’s explain in a real-life example:
It’s Friday and John wants to spend this evening with his friend Bob, scarfing pizza and watching one of the Star Wars episodes.
sync – John finishes his work. Then goes and orders the pizza, waits till it’s done. Then picks up his friend. And finally (with Bob and pizza) makes it home and gets down to the movie.
async – John orders his pizza online, phones Bob, invites him to come. He heads home, has his pizza delivered and starts watching the movie (and eating the pizza) without waiting for Bob to show up.
reactive – John orders pizza, phones Bob, invites him to come, heads home, and gets his pizza delivered. But this time, he waits until Bob comes and only after that he turns the movie on.
More about Reactive Programming in Java in the upcoming tech-news.