Reactive Programming in Java: How, Why, and Is It Worth Doing? Blocking I/O
Traditional I/O is blocking. What is a blocking I/O operation?Suppose you are reading a file or a database. You call a method that does it, and it blocks the thread – you are doing nothing then, just waiting. For example, you called readFile() and wait for it to execute. The thread is blocked and not progressing – it’s on wait. But in fact, the processor is not busy.
In this example, the following threads are blocked:
- Blocked on reading file
- Blocked on reading from DB
- Blocked on heavy calculations
- Blocked on responding to the client
This situation is similar to one you might encounter in a supermarket, and there are four checkout points, but the service system is lagging. The cashiers are doing nothing, just waiting for the cash register response when pressing a button.
What should you do when all threads are blocked? How can such problems be resolved in a supermarket?
Synchronous I/OStandard option: synchronous I/O. It’s not very good, as in this case, there will be lines to checkout points.
What could be done to avoid huge lines? You can, for example, open more checkout points or create more threads.
More threads mean more checkout points. This is a working option. But the load would be uneven.
You open more checkout points (create more threads), but it turns out that some of them remain idle. Actually, it’s not just standing idle – when you get multiple threads, you have additional consumption of resources. You need more memory. Besides, the processor needs to switch between threads.
The more threads, the more frequently you have to switch between them. You get more threads than cores. Suppose you have 4 cores but created a hundred threads because all the rest were blocked on reading data. Therefore, there is so-called context switching so that different threads can get their portion of computing time.
But this approach has certain disadvantages. Context switching is not free. It takes time. Making an infinite number of threads would be a good option in theory. But in practice, we get decreasing speed and growing memory consumption.
In Java, there are various approaches to prevent it – these are BlockingQueue and ThreadPool. You can limit the number of threads, and then all other clients will line up in the queue. At the start, you can have a minimal number of threads; then, their number is growing.
Look at the supermarket example again: if there are not too many people, then two checkout points are open, while at a rush hour, ten checkout points are working. But you cannot open more because you will have to rent extra space and hire more people. And this will hurt the business.
Now let’s discuss some new trends, such as self-checkout, preorders, etc. That means we are getting closer to the asynchronous approach.
Asynchronous I/OAsynchronous I/O has long been in use, as we need it when utilizing very slow I/O tools. And the slowest I/O device you always deal with is not a console or keyboard but humans. Asynchronous systems that interact with humans appeared a long time ago.
Blocking interfaces were used in machine-human interactions—for example, the old DOS command-line interface. There are still utilities that ask a question, then are blocked and do nothing but wait for the user’s response. Asynchronous I/O has been used since window interfaces began to appear. Currently, most interfaces are asynchronous.
The original article can be found here.
Interested in learning how to program with Java or in upgrading your Java programming skills? Check out our trainings.