Blocking I/O and non-blocking I/O

pa324·2020년 6월 12일

URL Schema

  • bigint(unsigned) : 0 ~ 18446744073709551615
  • Most browsers will put very large amounts of data in a URL and thus lots of things end up creating very large URLs so I will need to use a TEXT column since the VARCHAR/CHAR are limited.

Data Flow Diagram

Spring Framework

Spring Core

  • The Spring Framework provides the necessary infrastructure for web application development.
  • Developers can focus on developing business logic.
  • The Spring Framework is divided into Servlet Stack and Reactive Stack. Reactive Stack handles requests with non-blocking for next-generation cores.
  • In the case of servlet stack, spring MVC project is generally used.
  • Spring Core provides IoC,DI,AOP,PSA.
    • AOP is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.
    • If you manage the cache using the CacheManager in the Spring Framework, there is no code change even if the technology changes.
    • If we want to add our own behavior, we need to extend the classes of the framework or plugin our own classes.

Spring MVC

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. Let's look at the process of processing web requests using spring mvc.

1) The client sends an HTTP request to a specific URL

2) DispatcherServlet of Spring MVC receives the request

2) It passes the request to a specific controller depending on the URL requested using @Controller and @RequestMapping annotations.

3) Spring MVC Controller then returns a logical view name and model to DispatcherServlet.

4) DispatcherServlet consults view resolvers until actual View is determined to render the output

5) DispatcherServlet contacts the chosen view (like Thymeleaf, Freemarker, JSP) with model data and it renders the output depending on the model data

6) The rendered output is returned to the client as a response

I/O Model

  • I/O operations cannot be performed directly in user mode.
  • The actual I/O work is done at the kernel level. At this time, the user (process or thread) requests I/O operations through the system call to the kernel.
  • At this time, it can be divided into various models according to the method of sending and receiving system calls to the kernel end at the user level.
  • Depending on how I/O work is handled, it can be divided into a blocking model and a non-blocking model.

Blocking Model

  • Blocking model is a method that sends a response after the system call to the request is completed while entering the operating system Ready Queue.
  • The blocking model has to wait until the request is completed, causing the CPU to be idle.

Non-Blocking Model

  • The Non-Blocking model does not enter the operating system Ready Queue when an application is executed, and sends a response regardless of whether it is executed or not. At this time, the task that cannot return the response immediately returns an error and sends the request again until normal data is received.
  • The non-blocking model has the advantage of making the CPU uninterrupted in the blocking model, but has the disadvantage of making repeated requests. So, it is the I/O event notification model that appeared.

Event notification model

The method of notifying I/O events can be divided into Synchronous Model and Asynchronous Model. Let's see the difference between the two.



  • Without waiting for the stem call, it responds to the request regardless of whether it has been processed or not, and then moves on to the next code. Thereafter, the OS stage informs whether the processing is completed and responds.
  • This is a method of continuing to do one's work and processing when notification is received by Event Handlelr.
  • In other words, the notify is handled by the kernel, so the user process is passive, and when notify comes, it processes the I/O.

Difference between Blocking, Non-Blocking, Async, Sync

  • Blocking/Non Blocking's concern is whether or not the function being called returns immediately.
  • Async/Sync's concern is who cares whether the function being called completes?
  • Pass the callback to the called function, and when the operation of the called function is completed, the called function executes the received callback and the calling function does not care whether the operation is completed or not. Async.
  • Even if the calling function waits for a return after completing the operation of the calling function or receives a return from the calling function immediately, the function that calls the task completion is Sync if it checks itself.

Synchronous Task vs Asynchronous Task

Synchronous Task

  • A SlowWorker instance is created and the doWork() is invoked.
  • Then the main method tries to perform some other important task – printing to the system output – while (!) the doWork() churns away on its task.
  • However, since this is a synchronous call, this attempt at parallel activity fails.

public class SlowWorker {
    public SlowWorker() {
    public void doWork() {
        try {
            System.out.println("==== working, working, working ====== ");
            System.out.println("==== ready! ======");
        } catch (InterruptedException e) {
    public static void main(String[] args) {
        SlowWorker worker = new SlowWorker();
        System.out.println("Start Work"  + new java.util.Date());
        System.out.println("... try to do something while the work is being done....");
        System.out.println("End work" + new java.util.Date());

Asynchronous Task

  • Let's see how to process asynchronously.
package future;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AsynchronousWorker {
    public AsynchronousWorker() {
    public static void main(String[] args) {
        System.out.println("Start Work"  + new java.util.Date());
        ExecutorService es = Executors.newFixedThreadPool(3);
        final Future future = es.submit(new Callable() {
                    public Object call() throws Exception {
                        new SlowWorker().doWork();
                        return null;
        System.out.println("... try to do something while the work is being done....");
        System.out.println("... and more ....");
        try {
            future.get(); // blocking call - the main thread blocks until task is done
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        System.out.println("End work" + new java.util.Date());

JAVA Asynchronous API


  • When submitting a job to the ExecutorService, the job is appropriately scheduled and internally scheduled within the ExecutorService.

  • At this time, the threads in the ThreadPool in the queue with the task each process their own task.

  • From the developer's point of view, there is no need to separately manage the life cycle of the threads.

  • Two types are provided for the ExecutorService, and classes implemented by inheriting Runnable and Callable are received.

  • It makes it easy to run asynchronous tasks in Java.

  • This interface is supported from JDK 1.5.

  • ExecutorService provides thread pool and API for task assignment

    public static void asyncTask2() throws ExecutionException, InterruptedException {

        ExecutorService threadpool = Executors.newCachedThreadPool();

        threadpool.execute(() -> {
            try {
            } catch (InterruptedException e) {}
            System.out.println("Async tasek2 test");




  • Future is used to express asynchronous processing result. Provides methods to check whether asynchronous processing is completed, wait for processing completion, and return processing results.
  • When getting the asynchronous result, use the get method, but the thread is blocked until the asynchronous operation is completed.
  • The future performs an asynchronous operation or operation and has the result.
  • The get() method waiting for completion and returning the result
  • Provide isDone() method to check if the operation is completed.
   public static void asyncTask3() throws ExecutionException, InterruptedException{

        ExecutorService threadPool = Executors.newCachedThreadPool();

        Future<String> f = threadPool.submit(()-> {

                return "Async task3 test";




  • FutureTask creates an asynchronous task.
  • Separate asynchronous task creation and execution
public static void asyncTask4() throws ExecutionException, InterruptedException{
        ExecutorService threadPool = Executors.newCachedThreadPool();

        FutureTask<String> f = new FutureTask<>(() -> {
            return "Asyncrhonous task4 test";



FutureTask with callback pattern

  • When using a callback to get the result of an asynchronous operation (Future does not use a handler such as get())
  • Override the done() method that is called when the FutureTask's asynchronous task is completed to use callback
public static void asyncTask5() throws ExecutionException,InterruptedException {

        ExecutorService threadPool = Executors.newCachedThreadPool();

        FutureTask<String> f = new FutureTask<String>(() -> {
            return "Asyncrhonous task5 test";
        }) {
            protected void done() {
                } catch (InterruptedException e) {
                } catch (ExecutionException e) {



  • In Java 8, the CompletableFuture class was introduced.
  • CompletableFuture is an extension to Java’s Future API which was introduced in Java
    • Limitations of Future
      • It can not be manually completed
      • Multiple Futures can not be chained together
      • You can not combine multiple Futures together
      • No Exception Handling
  • This interface defines the contract for an asynchronous computation step that can be combined with other steps.

Using CompletableFuture as a Simple Future

public Future<String> calculateAsync() throws InterruptedException {
    CompletableFuture<String> completableFuture 
      = new CompletableFuture<>();
    Executors.newCachedThreadPool().submit(() -> {
        return null;
    return completableFuture;

Combining Futures

CompletableFuture<String> completableFuture 
  = CompletableFuture.supplyAsync(() -> "Hello")
    .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));

Java Blocking/Non-Blocking API

When a client makes a request to a server, server processes the request and sends back a response. For this to happen, both the client and the server first needs to establish a connection with one another. Both the client and the server has to bind itself to a socket at the end of it's connection and the server waits listening to its socket for the client to make a reuqest for a connection.

Blocking I/O

The Java blocking IO API is included in JDK under the package and is generally the simplest to use.

With blocking I/O, when a client makes a request to connect with the server, the thread that handles that connection is blocked until there is some data to read, or data is fully written. Until the relevant operation is complete that thread can do nothing else but wait. Now to fulfill concurrent requests with this approach we need to have multiple threads, that is we need to allocate a new thread for each client connection.

  • Each thread requires a stack of memory allocated to it and with the increase number of connections, spawning multiple thread and switching between them will become cumbersome
  • At any give point in time there can be multiple threads just waiting for the client requests and that is just a waste of resources.
  • Therefore, this blocking I/O approach is not ideal if you have to cater to a large number of clients, but to a small to a moderate number of clients this would to just fine.

Non-blocking I/O

Java.nio is a non-blocking API for socket connections, which mean clients are not tight to the number of threads available. With this library, one thread can handle multiple connections at once.

With non-blocking I/O, we can use a single thread to handle multiple concurrent connections. java NIO has a class called "Selector" that allows a single thread to examine I/O events on multiple channels. This selector can check the readliness of a channel for operations, such as reading and writing

  • channel : channels are a combinations of input and output streams, they allows you to read and write, and they use buffers to do these operations.

  • buffer : it is a block of memory used to read from channel and write into it.

  • selector : A Selector can register multiple Channels and will check which ones are ready for accepting new connections. Only one thread is required to handle multiple connections.

  • selector key : It contains properties for a particular Channel.


0개의 댓글