본문으로 건너뛰기
김신건의 로그

Synchronous (동기)

· 수정 · 📖 약 2분 · 904자/단어 #concurrency #programming-model #sync-async
synchronous, sync, 동기, 동기식, 동기 호출, synchronous call

정의

Synchronous (동기) 는 호출자가 결과가 나올 때까지 기다리는 실행 모델. 함수를 호출한 코드는 그 함수가 반환할 때까지 다음 줄로 진행하지 않는다.

이 정의는 Java 의 synchronized 키워드와는 별개의 개념 이다. synchronized 는 동시성 제어 (mutual exclusion), Synchronous 는 호출 모델 (control flow).

Asynchronous 와의 대비, 그리고 Blocking 과의 관계가 핵심.

가장 단순한 형태

// Synchronous
String result = http.get("/api/users");   // 응답이 올 때까지 대기
process(result);                          // 그 다음 진행

호출자는 응답이 오기 전까지는 다음 줄을 실행하지 않는다. 코드 작성과 디버깅이 가장 쉽다.

Sync 의 두 형태, blocking vs non-blocking

Synchronous 는 호출자가 결과를 기다린다 는 의미이지, 그 동안 CPU 를 점유 하는지는 별개. 따라서 두 가지가 가능.

Synchronous + Blocking (가장 흔함)

호출자가 결과를 기다리는 동안 스레드가 멈춘다 (block). OS 가 스레드를 sleep 상태로 만들고 CPU 를 다른 스레드에게 양보.

// 전형적인 sync + blocking
Socket s = serverSocket.accept();         // 연결이 올 때까지 block
String line = reader.readLine();          // 데이터가 올 때까지 block
Thread.sleep(1000);                       // 1초 block

Synchronous + Non-Blocking (드물지만 가능)

호출자가 결과를 기다리지만 스레드는 활성 상태 로 계속 돈다. busy-waiting 또는 polling.

while (!result.isReady()) {
    Thread.onSpinWait();   // hint, 스레드 양보 안 함
}
String value = result.get();

CPU 를 낭비하므로 거의 안 쓴다. 매우 짧은 대기에만 쓰이는 마이크로 최적화.

동기 호출 흐름

[caller]                    [callee]

   │── call ───────────────►│
   │                         │ 작업 수행
   │ (waiting)              │
   │                         │
   │◄─────────── return ────│

   │ 결과 처리 시작

호출자와 호출자의 스택 프레임이 일직선 으로 이어진다. 디버깅 시 stack trace 가 그대로 보인다.

장점

  • 읽기 쉽다, 위에서 아래로 흐름이 명확
  • 에러 처리가 자연스럽다, try / catch 가 호출자 스택에 닿는다
  • 디버깅이 쉽다, breakpoint 가 작동, stack trace 가 일관
  • 테스트가 쉽다, 순차 실행이라 결과 비교 단순

단점

  • 느린 작업은 스레드를 묶는다, 동시에 1000명 응답하려면 1000개 스레드
  • CPU 활용도가 낮을 수 있다, I/O 대기 중에는 CPU idle
  • 응답성 저하, 한 요청이 다른 요청의 진행을 막을 수 있다

언제 동기가 적절한가

  • 응답이 빠르고 일관적, 5ms 이내의 DB 쿼리 같은 작업
  • 순차 흐름이 본질, 단계 A 의 결과로 단계 B 를 결정
  • 저부하 시스템, 동시 접속 < 수백 수준
  • 테스트, 스크립트, 마이그레이션 도구

언제 Asynchronous 가 더 나은가

  • 외부 API 호출 (수 초 걸릴 수 있음)
  • 수만 명 동시 접속을 받는 서버
  • UI 가 멈추면 안 되는 클라이언트
  • 여러 작업을 병렬로 진행해야 할 때

Java 의 동기 IO 예

HTTP 클라이언트 (sync)

// HttpClient 동기 모드
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(
    HttpRequest.newBuilder(URI.create("https://api.example.com")).build(),
    BodyHandlers.ofString()
);
// 응답이 도착할 때까지 block

File I/O (sync)

String content = Files.readString(Path.of("data.txt"));
// 파일을 다 읽을 때까지 block

Socket (sync)

ServerSocket server = new ServerSocket(8080);
Socket client = server.accept();           // 연결을 받을 때까지 block
String line = new BufferedReader(...).readLine();   // 데이터를 받을 때까지 block

Sync 모델의 동시성, 스레드 풀

대량 동시성을 위해 sync 코드는 스레드 풀 을 쓴다. 각 요청을 별도 스레드에 할당해 병렬 처리.

ExecutorService pool = Executors.newFixedThreadPool(100);
// 매 요청마다
pool.submit(() -> {
    String r = http.get(url);    // sync call, but in separate thread
    process(r);
});

이 방식의 한계:

  • 스레드 1개당 메모리 ~1MB
  • 100 스레드 ≈ 100MB
  • 10,000 동시 연결 → ~10GB, 비현실적

Java 21+ 의 Virtual Threads (JEP 444) 가 이 한계를 깬다. 동기 코드 그대로 두면서도 수백만 동시 연결을 가능하게 만든다.

sync 와 Blocking 의 관계

표현의미
synchronous호출자가 결과를 기다린다 (control flow)
blocking그 동안 스레드가 멈춘다 (thread state)

같이 쓰이지만 다른 차원. 대부분의 sync 코드는 blocking 이지만, non-blocking sync 도 가능 (Thread.onSpinWait(), busy-loop) 하다.

반대로 async + blocking 도 가능 (콜백 안에서 다시 sleep 하는 경우, 자주 보는 안티패턴).

참고

이 글의 용어 (3개)
Asynchronous (비동기)concurrency
정의 Asynchronous (비동기) 는 호출자가 결과를 즉시 기다리지 않고 다음 줄로 진행하는 실행 모델. 결과는 나중에 콜백, Future, Promise, 이벤트 등의 메…
Blocking (블로킹)concurrency
정의 Blocking 은 호출이 결과가 준비될 때까지 호출 스레드를 멈춰두는 실행 방식. 멈춰 있는 동안 그 스레드는 다른 일을 할 수 없다. OS 가 스레드를 sleep 상태로…
Non-Blocking (논블로킹)concurrency
정의 Non-Blocking 은 호출이 결과를 기다리지 않고 즉시 반환 하는 실행 방식. 결과가 준비되지 않았다면 "아직 안 됐다" 라는 신호 ( , , , 등) 를 반환하고, …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기