본문 바로가기

백엔드 면접준비/Java

7. 동기,비동기, 블로킹, 논블록킹

1. 기본 개념

개념 정의 예시
동기(Synchronous) 작업 요청 후 결과를 받을 때까지 기다림 전화 통화
비동기(Asynchronous) 요청 후 결과를 기다리지 않고, 완료되면 알림(콜백, Future,...) 문자 메세지
블로킹(Blocking) 요청한 작업이 끝날 때까지 현재 실행중인 작업이 멈춤 엘레베이터에서 기다리는 것
논블로킹(Non-blocking) 요청한 작업이 끝나지 않아도 다른 작업을 계속 수행 가능 식당에서 주문 후 다른 일을 하는 것

2. 동기(Synchronous) vs 비동기(Asynchronous)

동기(Synchronous)

  • 작업을 요청한 후 응답을 받을 때까지 기다려야함
  • 요청한 작업이 끝나야 다음 작업을 수행 가능 

1) 예제(동기 방식) - Java

public class SyncExample {
    public static void main(String[] args) {
        System.out.println("요청 시작");
        String result = blockingTask(2);//결과를 기다림
        System.out.println("result = " + result);
        System.out.println("작업 완료");
    }

    private static String blockingTask(int n) {
        try {
            for (int i = 1; i < n + 1; i++) {
                System.out.printf("----- %d초 대기 -----\n", i);
                Thread.sleep(i * 1000L);
            }
        } catch (Exception ignored){}
        return "동기 작업 완료";
    }
}

 

2) 출력결과

  • blockingTask(n) 메서드가 끝나야 다음 코드가 실행됨
요청 시작
----- 1초 대기 -----
----- 2초 대기 -----
result = 동기 작업 완료
작업 완료

 

비동기(Asynchronous)

  • 요청 후 결과를 기다리지 않고 즉시 다음 작업을 수행할 수 있음
  • 작업이 끝나면 콜백(callback) 또는 Future/CompletableFuture로 결과를 받아 처리

1) 예제(비동기 방식 - Java/CompletableFuture

public class AsyncExample {
    public static void main(String[] args) {
        System.out.println("작업 시작");

        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
            try {
                for (int i = 1; i < 2 + 1; i++) {
                    System.out.printf("----- %d초 대기 -----\n", i);
                    Thread.sleep(i * 1000L);
                }
            } catch (Exception e) {}
            return "\n비동기 작업 완료!";
        }).thenAccept(System.out::println);

        System.out.println("메인 쓰레드 종료");

        future.join();//비동기 작업이 끝날 때까지 대기
    }
}

 

2) 출력결과

  • thenAccept() : 콜백에 실행될 때까지 메인 스레드는 멈추지 않고 다른 작업을 수행
  • CompletableFuture.join : 비동기 작업이 완료될 때까지 블로킹방식으로 대기후 결과반환
작업 시작
----- 1초 대기 -----
메인 쓰레드 종료
----- 2초 대기 -----

비동기 작업 완료!

 


3. 블로킹(Blocking) vs 논블로킹(Non-blocking)

블로킹(Blocking)

  • 현재 실행 중인 스레드가 요청한 작업이 끝날 때까지 대기함
  • CPU 리소스를 비효율적으로 사용하게 될 수 있음

1) 예제 (블로킹 방식 - Java)

public class BlockingExample {
    public static void main(String[] args) {
        String filePath = "src/main/resources/data.json";
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            // 파일을 읽는 동안 다른 작업 불가능(블로킹)
            File file = new File(filePath);
            Map<String, Object> dataMap = objectMapper.readValue(file, new TypeReference<>() {});

            System.out.println("블로킹 방식으로 읽은 JSON 데이터");
            String prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(dataMap);
            System.out.println(prettyJson);
        } catch (Exception ignore){}
    }
}

 

2) 출력결과

  • 파일을 읽기 작업이 끝날때 까지 다른 처리는 실힝되지 않음
블로킹 방식으로 읽은 JSON 데이터
{
  "name" : "SeongSik Oh",
  "age" : 32,
  "job" : "Backend Dev",
  "isEmployed" : true
}

 

논블로킹(Non-blocking)

  • 작업 요청 후 즉시 반환되며, 작업이 끝나지 않아도 다른 작업을 수행 가능 
  • 일반적으로 이벤트 기반(eveng-driven) 방식으로 처리됨

1) 예제 (논블로킹 방식 - Java)

public class NonBlockingExample {
    public static void main(String[] args) {
        String filePath = "src/main/resources/data.json";
        ObjectMapper objectMapper = new ObjectMapper();

        // 비동기 작업으로 파일 읽기
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
            try {
                // 파일을 비동기적으로 읽기(논블로킹)
                File file = new File(filePath);
                Map<String, Object> dataMap = objectMapper.readValue(file, new TypeReference<>() {});

                return dataMap;
            } catch (Exception ignore) {
                return null;
            }
        }).thenAccept(dataMap -> {
            if(dataMap != null){
                try {
                System.out.println("\n논블로킹 방식으로 읽은 JSON 데이터");
                String prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(dataMap);
                System.out.println(prettyJson);
                } catch (Exception ignore) {}
            }
        });

        // 파일 읽기 작업이 완료되기 전에 다른 작업을 할 수 있음
        System.out.println("메인 메서드 실행종료");

        future.join();
    }
}

 

2) 출력결과

  • 파일을 읽는 동안 다른 작업을 계속 수행할 수 있음
메인 메서드 실행종료

논블로킹 방식으로 읽은 JSON 데이터
{
  "name" : "SeongSik Oh",
  "age" : 32,
  "job" : "Backend Dev",
  "isEmployed" : true
}

4. 동기/비동기 vs 블로킹/논블로킹 관계

동기/비동기 블로킹/논블로킹 설명
동기 + 블로킹 요청한 작업이 끝날 때까지 대기 일반적인 함수호출
동기 + 논블로킹 요청 후 바로 반환처리되지만, 결과를 즉시 확인행함 Future.get() 사용
비동기 + 블로킹 비동기 작업을 실행하지만, get()호출 시 블로킹된 CompletableFuture.get()
비동기 + 논블로킹 요청 후 바로 반환되고, 결과는 콜백 또는 이벤크로 처리됨 CompletableFuture.thenAccept(),
WebFlux

 

  • (Q) 비동기, 논블로킹의 차이는?
    • 비동기결과를 기다리지 않고 작업을 요청한 후 다른 작업을 진행하는 방식입니다. 하지만 일부 비동기 작업은 결과를 받을 때까지 스레드가 블로킹될 수 있다
    • 논블로킹스레드가 작업을 기다리지 않고 다른 작업을 할 수 있는 방식이다. I/O 작업에서 논블로킹 방식이 활용되며, 처리 중 다른 작업을 병렬적으로 수행할 수 있다.따라서, 비동기작업의 순서결과 처리 방식에 관한 개념이고, 논블로킹스레드의 차단 여부와 관련된 개념이다.

 

  • 동기(Synchronous): 요청한 작업이 끝날 때까지 기다림.
  • 비동기(Asynchronous): 요청 후 즉시 반환, 완료되면 콜백으로 처리.
  • 블로킹(Blocking): 작업이 끝날 때까지 현재 스레드가 멈춤.
  • 논블로킹(Non-blocking): 작업이 진행되는 동안에도 다른 작업을 수행할 수 있음.

5. WebFlux

  • WebFlux는 Reactor 기반으로 동작하는 비동기 논블로킹 프레임워크이다. 기본적으로 단일 쓰레드 모델로 동작할 수 있지만, 모든 작업을 하나의 쓰레드에서 처리하는 것은 아니다. 대신, 논블로킹 방식으로 다수의 작업을 효율적으로 처리하며, 필요에 따라 스레드 풀을 사용해 비동기 작업을 처리한다.

WebFlux와 스레드

  • 비동기 논블로킹 처리를 위해 Reactor의 스케줄러(Scheduler)를 활용하여 여러 스레드를 사용할 수 있다.

WebFlux 동작방식

  • 단일 스레드로 요청 처리가능
    • 기본적으로 WebFlux는 단일 이벤트 루프 방식으로 작동할 수 있다. 논블로킹 방식이기 때문에, 한 요청이 대기중일 때 다른 요청을 처리할 수 있다.
  • I/O 작업은 비동기적으로 처리
    • HTTP 요청을 보내거나, DB에서 데이터를 조회하거나, 다른 외부 서비스를 호출하는 등의 I/O 작업은 비동기 논블로킹 방식으로 처리된다. WebClient 같은 비동기 API를 사용하면, 해당 작업을 다른 스레드에서 실행하지 않고, 현재 스레드를 차단하지 않고 처리한다.(I/O 작업은 결과가 도달할 때까지 기다리지 않고 다른 작업을 계속 처리할 수 있다.)
  • 스레드 풀 사용
    • WebFlux는 기본적으로 I/O 작업을 처리하기 위해 스레드 풀을 사용한다. 예를 들어, HTTP 요청이나 DB 연결 등에서 비동기 작업이 이루어질 때, Webflux는 "boundedElastic()/parallel()"와 같은 스케줄러를 사용하여 추가적인 스레드를 활용할 수 있다.
    • 이를 통해 논블로킹 I/O를 최적화하고, CPU 집약적인 작업을 병렬로 처리할 수 있다.

WebClient와 스레드

  • WebClient는 비동기 논블로킹 방식으로 HTTP 요청을 처리하는 클라이언트이다. WebClient의 호출, 응답 처리는 기본적으로 Reactor 스케줄러에서 실행된다.

기본적인 WebClient 흐름

  1. retreive() : 비동기적으로 HTTP 요청을 보낸다. 이때 I/O 작업은 논블로킹 방식으로 처리되며, 별도의 스레드를 사용하여 요청을 보낸다.
  2. doOnSuccess(), doOnError() : 응답이 성공/에러가 발생하면, 해당 작업이 Reactor 스케줄러에서 실행된다. 기본적으로 WebClient는 Reactor의 기본 스케줄러를 사용하므로, 이 메서드들은 I/O 작접을 처리한 스레드에서 실행될 수 있다.
  3. subscribe() : 실제로 비동기 작업을 구독하는 메서드이다. subscribe()를 호출할 스레드에서 이 작업이 구독된다.

 

 

 

'백엔드 면접준비 > Java' 카테고리의 다른 글

JAVA 기본  (1) 2025.03.16
8.JAR  (1) 2025.03.10
6. Garbage Collection(GC)  (1) 2025.03.07
4. Java Annotation  (0) 2025.03.07
3. Java Collection Framework(JCF)  (1) 2025.02.03