Java

[JVM 밑바닥까지 파헤치기] 12-3. 자바 메모리 모델과 스레드: 스레드

noahkim_ 2024. 12. 26. 01:06

저우즈밍 님의 "JVM 밑바닥까지 파헤치기" 책을 정리한 포스팅 입니다

 

1. 스레드 구현

OS 스레드

  • 다양한 하드웨어와 운영체제에서 운용할 수 있는 통합된 개념
  • 핵심 메서드 모두가 네이티브 코드로 구현됨
항목 커널 스레드 (1:1) 사용자 스레드 (1:N) 하이브리드 스레드
구현 위치 커널(운영체제 수준) 사용자 공간 (라이브러리 수준) 커널 + 사용자 공간
매핑 방식 사용자 스레드 1개 ↔ 커널 스레드 1개 여러 사용자 스레드 ↔ 1개 커널 스레드
여러 사용자 스레드 ↔ 여러 커널 스레드
시스템 콜 필요 모든 스레드 작업에 필요 필요 없음 상황에 따라 사용
비용 높음 (커널 호출 발생) 낮음 (컨텍스트 스위치 빠름) 중간
병렬 처리 가능 (다중 CPU 활용 가능) 불가능 (동시 실행 불가) 가능
유연성 낮음 높음 (유저가 직접 관리) 높음
단점 커널 자원 한정, 생성 비용 큼 하나가 블로킹되면 전체 정지 가능성 구현 복잡도 높음

 

 

자바 스레드

  • 커널 스레드로 구현됨
  • 운영체제 스케줄러에서 스케줄링 담당 (JVM 자체가 스케줄링을 구현하지 않음)
    • 선점형 스케줄링: 스레드 우선순위를 통해 먼저 할당되도록 권고할 수 있음 (Thread.setPriority())
    • 프로세서 할당: 스레드 간에 CPU 사용 권한은 운영체제의 스케줄러가 제어함

 

2. 자바와 가상 스레드

  • 운영체제별 스레드 모델의 차이를 숨기는 통합 인터페이스를 제공

 

커널 스레드의 한계

마이크로서비스 아키텍처
  • 하나의 서비스를 분산시키고 협력을 통해 최종 응답 생성
  • 서비스 각각의 복잡성을 줄이고 재사용성을 높임
  • MSA 같이 동시 요청이 많을 때 병목이 됨 (생성, 전환, 블로킹 비용이 크고, 많이 만들기 힘들기 때문)

 

대안: 사용자 스레드 방식

항목 코루틴 (Coroutine)
가상 스레드 (Virtual Thread, JDK 21+)
정의 비동기 작업을 동기처럼 표현하는 경량 실행 단위
JVM이 관리하는 경량 스레드
실행 주체 사용자 공간에서 동작
(언어 런타임 또는 라이브러리 수준 스케줄링)
JVM 내부에서 사용자 모드 스케줄링
스레드 수 제한
전환 방식 명시적 suspend/resume
(개발자가 직접 지점 지정)
JVM이 자동으로 중단/복원 (스택 프레임까지 관리)
스택 구조 스택리스 (상태를 클로저에 저장)
스택풀 (실제 스레드처럼 스택 프레임 전체를 JVM이 관리)
코드 스타일 비동기식 (suspend, await 필요)
기존 자바 동기식 코드와 동일 (Thread API 그대로 사용)
복잡도 구현 복잡도 높음 (호출 스택 복원, 상태 보존 등)
디버깅 어려움
기존 스레드와 동일한 디버깅 패턴 적용 가능
컨텍스트 스위칭 비용 낮음 (사용자 공간에서 전환. OS 개입 없음)
낮음 (JVM 내부 전환, OS 개입 없음)
블로킹 처리 직접 비동기 API를 사용해 non-blocking 처리 필요
내부적으로 블로킹 시 다른 가상 스레드에게 양보
대표 예시 Kotlin Coroutine
Go Routine
Python asyncio 
Java 21 Thread.startVirtualThread()
Java 21 Executors.newVirtualThreadPerTaskExecutor()