비동기(Asynchronous) vs. 동기(Synchronous), 블로킹(Blocking) vs. 논블로킹(Non-blocking)

49 sec read

개발을 하다 보면 ‘동기/비동기’, ‘블로킹/논블로킹’이라는 말을 정말 많이 듣게 됩니다. 비슷해 보이지만 명확히 다른 이 개념들, 처음에는 헷갈리기 쉽습니다. 하지만 서버 성능과 효율적인 코드 작성을 위해 반드시 알아야 할 핵심 개념이기도 합니다.

이번 포스트에서는 각 개념을 명확히 구분하고, 실제 자바(Java) 코드 예제를 통해 어떻게 동작하는지 확실하게 정리해 드리겠습니다.


1. 실행 순서: 동기(Synchronous) vs. 비동기(Asynchronous)

두 개념은 작업 간의 실행 순서를 결정하는 방식에 대한 이야기입니다.

동기(Synchronous)란?

요청을 보낸 후, 해당 요청의 응답이 올 때까지 기다리는 방식입니다.

한 작업이 완전히 끝나야만 다음 작업을 시작할 수 있는, 순차적인 처리 방식입니다.

  • 특징: 작업이 순서대로 실행되어 흐름을 이해하기 쉽습니다.
  • 예시: 전화 통화 📞
    • 내가 말을 하면 상대방이 듣고 대답할 때까지 기다려야 다음 대화를 이어갈 수 있습니다.

비동기(Asynchronous)란?

요청을 보낸 후, 응답을 기다리지 않고 즉시 다음 작업을 실행하는 방식입니다.

요청한 작업이 언제 끝날지 신경 쓰지 않고, 일단 다른 작업을 계속 진행합니다. 나중에 응답이 오면 그때 처리합니다.

  • 특징: 여러 작업을 동시에 처리할 수 있어 시스템 자원을 효율적으로 사용할 수 있습니다.
  • 예시: 문자 메시지 💬
    • 메시지를 보내놓고 상대방이 언제 답장할지 모르지만, 그동안 다른 일을 할 수 있습니다. 답장이 오면 그때 확인하면 됩니다.

2. 제어권: 블로킹(Blocking) vs. 논블로킹(Non-blocking)

두 개념은 요청한 작업이 자원을 사용하는 동안, 제어권이 누구에게 있는지에 대한 이야기입니다.

블로킹(Blocking)이란?

요청한 작업이 완료될 때까지 현재 실행 중인 스레드를 멈추고 기다리는 방식입니다.

A 함수가 B 함수를 호출하면, B 함수의 실행이 완전히 끝날 때까지 A 함수는 멈춘 상태(대기 상태)로 제어권을 넘겨줍니다.

  • 특징: 호출된 함수의 작업이 끝날 때까지 다른 작업을 수행할 수 없습니다.
  • 예시: 파일 읽기 작업 💾
    • 파일을 읽는 코드를 실행하면, 파일을 다 읽어올 때까지 프로그램이 잠시 멈춥니다.

논블로킹(Non-blocking)이란?

요청한 작업을 시작한 후, 완료 여부와 상관없이 즉시 제어권을 반환하여 다음 코드를 실행하는 방식입니다.

A 함수가 B 함수를 호출해도 제어권을 계속 가지고 있으면서 다른 작업을 수행할 수 있습니다.

  • 특징: 호출한 작업의 완료를 기다리지 않고 다른 작업을 계속할 수 있습니다.
  • 예시: 논블로킹 파일 읽기
    • 파일 읽기를 요청한 후 즉시 제어권을 돌려받아 다른 코드를 실행하고, 파일 읽기가 완료되면 별도의 알림(콜백)을 통해 데이터를 처리합니다.

개념 한눈에 비교하기

구분동기 (Synchronous)비동기 (Asynchronous)블로킹 (Blocking)논블로킹 (Non-blocking)
핵심 개념작업의 순서작업의 순서제어권의 흐름제어권의 흐름
설명요청 후 응답을 기다림요청 후 응답을 기다리지 않음요청한 작업이 끝날 때까지 멈춤요청한 작업이 끝나지 않아도 즉시 반환
예시전화 통화문자 메시지대기 줄이 긴 맛집에서 기다리기맛집에 대기 명단 올려놓고 다른 곳 구경하기
코드 실행 흐름순차적으로 진행다른 작업과 병행 가능실행이 멈춤 (대기)실행이 멈추지 않음

4가지 조합으로 개념 완벽 이해하기 (Java 코드 예제)

이제 네 가지 개념을 조합하여 실제 코드에서 어떻게 나타나는지 살펴보겠습니다.

1. 동기 + 블로킹 (Sync + Blocking)

가장 일반적이고 직관적인 모델입니다. 요청을 보내고, 작업이 끝날 때까지 멈춰서 기다립니다.

public class SyncBlockingExample {
    public static void main(String[] args) {
        System.out.println("작업 시작");
        try {
            // 이 코드를 호출한 main 스레드는 3초 동안 멈춥니다(Blocking).
            Thread.sleep(3000); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("작업 완료");
    }
}
  • 실행 흐름
    1. "작업 시작" 출력
    2. Thread.sleep(3000)이 호출되고, main 스레드는 3초 동안 블로킹(멈춤)됩니다.
    3. 3초 후, 제어권이 돌아오고 "작업 완료"가 출력됩니다.

2. 동기 + 논블로킹 (Sync + Non-blocking)

요청을 보내고 바로 제어권을 돌려받지만, 응답이 왔는지 주기적으로 직접 확인(Polling)합니다.

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

        long startTime = System.currentTimeMillis();
        // 작업이 끝났는지 계속 확인하지만, 스레드가 멈추진 않습니다(Non-blocking).
        while (System.currentTimeMillis() - startTime < 3000) {
            // 다른 작업을 할 수 있지만, 계속 상태를 확인하느라 바쁩니다.
        }

        System.out.println("작업 완료");
    }
}
  • 실행 흐름
    1. "작업 시작" 출력
    2. while 루프는 3초가 지났는지 멈추지 않고(Non-blocking) 계속 확인합니다.
    3. 하지만 결과가 나올 때까지 기다렸다가 다음 코드로 넘어가는 동기 방식입니다.
    4. CPU 자원을 계속 사용하므로 비효율적입니다.

3. 비동기 + 블로킹 (Async + Blocking)

조금 특이한 조합입니다. 다른 스레드에서 비동기적으로 작업을 시작했지만, 그 결과를 얻기 위해 결국 멈춰서 기다리는 경우입니다.

import java.util.concurrent.*;

public class AsyncBlockingExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        System.out.println("작업 요청 (비동기)");
        // 별도의 스레드에서 작업을 실행 (비동기)
        Future<String> future = executor.submit(() -> {
            Thread.sleep(3000);
            return "비동기 작업 완료";
        });

        System.out.println("다른 작업 수행 가능");

        // 결과를 가져오기 위해 멈춰서 기다립니다 (블로킹)
        String result = future.get(); 
        System.out.println(result);

        executor.shutdown();
    }
}
  • 실행 흐름
    1. executor.submit()으로 별도 스레드에서 3초짜리 작업을 비동기로 시작합니다.
    2. main 스레드는 멈추지 않고 "다른 작업 수행 가능"을 바로 출력합니다.
    3. future.get()을 만나는 순간, 비동기 작업의 결과가 올 때까지 main 스레드는 블로킹(멈춤)됩니다.
    4. 3초 후 비동기 작업이 끝나면 결과가 반환되고, 프로그램이 종료됩니다.

4. 비동기 + 논블로킹 (Async + Non-blocking)

가장 효율적인 모델입니다. 비동기적으로 작업을 요청하고, 멈추지 않고 다른 일을 하다가, 작업이 완료되면 콜백(Callback) 함수를 통해 결과를 처리합니다.

import java.util.concurrent.CompletableFuture;

public class AsyncNonBlockingExample {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("메인 스레드: 작업 시작");

        // 비동기 작업 시작 + 완료되면 실행할 콜백 함수 등록 (논블로킹)
        CompletableFuture.supplyAsync(() -> {
            System.out.println("별도 스레드: 작업 처리 중...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {}
            return "비동기 작업 완료";
        }).thenAccept(result -> {
            // 작업이 완료되면 이 부분이 실행됩니다.
            System.out.println("메인 스레드: 콜백 실행! 결과: " + result);
        });

        System.out.println("메인 스레드: 다른 작업 수행 중...");

        // 메인 스레드가 먼저 종료되는 것을 방지하기 위해 잠시 대기
        Thread.sleep(5000); 
    }
}
  • 실행 흐름
    1. supplyAsync()비동기 작업을 요청하고, main 스레드는 멈추지 않고(Non-blocking) 바로 다음 코드로 넘어갑니다.
    2. "메인 스레드: 다른 작업 수행 중..."이 즉시 출력됩니다.
    3. 별도의 스레드에서 3초간의 작업이 진행됩니다.
    4. 3초 후 작업이 완료되면, thenAccept()에 등록된 콜백 함수가 실행되어 결과를 출력합니다.

최종 정리

블로킹 (Blocking)논블로킹 (Non-blocking)
동기 (Synchronous)Sync + Blocking
(가장 흔한 모델)
Thread.sleep()
Sync + Non-blocking
(주기적 확인, 비효율적)
while 루프 Polling
비동기 (Asynchronous)Async + Blocking
(결과를 기다리며 블로킹)
future.get()
Async + Non-blocking
(가장 효율적인 모델)
CompletableFuture
  • 동기 + 블로킹: 호출한 함수가 끝날 때까지 기다리며 멈춘다.
  • 동기 + 논블로킹: 함수 호출 후 바로 반환받지만, 결과가 나올 때까지 주기적으로 확인하며 기다린다.
  • 비동기 + 블로킹: 별도 스레드에서 실행시키지만, 결국 결과를 얻기 위해 멈춰서 기다린다.
  • 비동기 + 논블로킹: 별도 스레드에서 실행시키고 신경 끄고 다른 일을 하다가, 결과가 오면 콜백으로 처리한다.

이제 동기/비동기, 블로킹/논블로킹의 차이점이 확실히 정리되었을 거예요! 이 개념들을 잘 활용하여 더 효율적이고 성능 좋은 코드를 작성해 보세요. 🚀

교보문고-실수로 배우는 자바 [POD]
교보문고-실수로 배우는 자바 [EBOOK]

루아 Lua 프로그래밍 : 모듈과 패키지 가이드

지금까지 우리는 함수로 코드를 묶고, 테이블로 데이터를 구조화하는 방법을 익혔습니다. 하지만 프로젝트의 규모가 커지기 시작하면, 모든 코드를 단 하나의 파일에 담는 것은 금세 한계에...
eve
53 sec read

루아 (Lua) 프로그래밍: 테이블과 메타테이블의 모든 것

Lua 프로그래밍의 여정에서 가장 중요하고 흥미로운 지점에 도달했습니다. 바로 Lua 언어의 심장이자 가장 중심적인 기능인 테이블(Table)입니다. Lua에는 배열, 딕셔너리, 리스트, 객체 등을 위한 별도의...
eve
1 min read

루아(Lua) 프로그래밍: 제어 구조 조건과 반복

지금까지 우리는 변수에 데이터를 저장하고, 연산자로 이 데이터들을 계산하고 비교하는 방법을 배웠습니다. 하지만 프로그램이 단순히 위에서 아래로 순서대로만 실행된다면, 매우 단순한 작업밖에 할 수...
eve
1 min read