Java/JVM
[JVM 밑바닥까지 파헤치기] 12-3. 자바 메모리 모델과 스레드: 스레드
noahkim_
2024. 12. 26. 01:06
저우즈밍 님의 "JVM 밑바닥까지 파헤치기" 책을 정리한 포스팅 입니다
1. 스레드 모델
- 사용자 수준의 스레드와 운영체제의 커널 스레드를 어떻게 매핑하고 관리할 것인지에 대한 구조적 설계 방식
종류
| 항목 | 커널 스레드 | 사용자 스레드 | 하이브리드 스레드 |
| 구현 위치 | 커널 (운영체제 수준) | 사용자 공간 (라이브러리 수준) | 커널 + 사용자 공간 |
| 매핑 방식 | 커널 스레드 1개 ↔ 사용자 스레드 1개 | 커널 스레드 1개 ↔ 사용자 스레드 N개 | 커널 스레드 N개 ↔ 사용자 스레드 N개 |
| 시스템 콜 | 모든 스레드 작업에 필요 | ❌ | 상황에 따라 사용 |
| 비용 | 높음 | 낮음 (컨텍스트 스위치 빠름) | 중간 |
| 병렬 처리 | ✅ | ❌ (동시 실행 불가) | ✅ |
| 유연성 | 낮음 | 높음 (유저가 직접 관리) | 높음 |
| 단점 | 커널 자원 한정, 생성 비용 큼 | 하나가 블로킹되면 전체 정지 | 구현 복잡도 높음 |
- 핵심 메서드 모두가 네이티브 코드로 구현됨
2. 자바 스레드
- 커널 스레드로 구현됨
- ✅ 운영체제별 스레드 모델의 차이를 숨기는 통합 인터페이스를 제공
- ➡️ 프로세서 할당: 스레드 간에 CPU 사용 권한은 운영체제의 스케줄러가 제어함
- ➡️ 선점형 스케줄링: 스레드 우선순위를 통해 먼저 할당되도록 권고할 수 있음 (Thread.setPriority())
커널 스레드의 한계
- ⚠️ 분산 시스템에서 동시 요청이 많을 때 병목이 됨
- 생성, 전환, 블로킹 비용이 크고, 생성 수가 한정되어 있음
대안 (사용자 스레드 방식)
| 항목 | 코루틴 |
가상 스레드
|
| 정의 | 비동기 작업을 동기처럼 표현하는 경량 실행 단위 |
JVM이 관리하는 경량 스레드
|
| 실행 주체 | 사용자 공간 |
JVM 내부에서 사용자 모드 스케줄링
|
| 전환 방식 | 명시적 suspend/resume |
JVM이 자동으로 중단/복원 (스택 프레임까지 관리)
|
| 스택 구조 | 스택리스 (상태를 클로저에 저장) |
스택풀 (실제 스레드처럼 스택 프레임 전체를 JVM이 관리)
|
| 코드 스타일 | 비동기식 (suspend, await 필요) |
기존 자바 동기식 코드와 동일 (Thread API 그대로 사용)
|
| 복잡도 | 높음 (호출 스택 복원, 상태 보존 등) |
기존 스레드와 동일한 디버깅 패턴 적용 가능
|
| 전환 비용 | 낮음 (사용자 공간에서 전환) |
낮음 (JVM 내부 전환)
|
| 블로킹 처리 | 직접 비동기 API를 사용해 non-blocking 처리 |
내부적으로 블로킹 시 다른 가상 스레드에게 양보
|
| 대표 예시 | - Kotlin Coroutine - Go Routine - Python asyncio |
- Java 21 Thread.startVirtualThread()
- Java 21 Executors.newVirtualThreadPerTaskExecutor() |