- Java의 Tread 클래스는 동시성 프로그래밍의 기초이자, 과거부터 지금가지 진화해 온 병렬처리의 기반 도구입니다.
- 스레드와 동기화는 고성능 애플리케이션 개발에 필수적인 요소입니다.
- 멀티스레드 환경에서 자원을 안전하게 공유하고 효율적으로 관리하기 위해 동기화는 필수적입니다.
1. Thread 클래스란?
java.lang.Thread
- Java에서 독립 실행 흐름(스레드)를 만들기 위한 핵심 클래스
- JVM 내부의 스레드 스케줄러에 의해 관리되며, OS의 실제 스레드에 매핑됩니다.
2. 주요 필드 및 핵심 기능
| 기능 |
설명 |
| start() |
새로운 스레드를 시작하여 run() 실행 |
| run() |
스레드에서 실행할 로직 정의 (Runnable 인터페이스 기반) |
| join() |
해당 스레드가 끝날 때까지 대기 |
| sleep(ms) |
스레드를 일정 시간 정지시킴 (CPU 사용 없음) |
| yield() |
CPU를 다른 스레드에게 양보하려 시도(우선순위 힌트를 제공) |
| interrupt() |
스레드에 인터럽트 신호 전달 |
| isAlive() |
스레드가 실행 중인지 확인 |
| setDaemon(true) |
데몬 스레드 지정 (JVM 종료 조건과 무관) |
3. 현재 Thread의 실무 사용률과 문제점
| 항목 |
설명 |
직접 사용 빈도
|
매우 낮아짐 (대부분 Executors, CompletableFuture, ForkJoinPool, VirtualThread 사용) |
| 단점 |
스레드 생성 비용 큼 (JVM ↔ OS 스레드 1:1)생성 수 제한 존재 (수천 개 수준)직접 제어 로직이 많아 복잡 |
| 특징 |
여전히 학습용, 데모용, 시스템 수준 코드에서 사용됨 |
Thread의 근본적 한계
| 한계 |
상세 설명 |
| 비싼 생성 비용 |
스레드를 만들 때마다 JVM은 OS 스레드를 요청 → 리소스 소모 큼 |
| 제한된 동시성 처리 수 |
수천 개 스레드 이상 생성 시 OutOfMemoryError: unable to create native thread 발생 |
| 직접 관리 필요 |
start(), join(), interrupt() 등 직접 호출해야 하고 예외 처리도 복잡 |
| 작업 단위가 없음 |
스레드는 실행 단위가 아님 → 별도 큐나 관리 계층이 필요 |
| Context Switching 비용 |
많은 스레드가 대기/준비 상태를 반복하면 커널 컨텍스트 스위칭 비용이 커짐 |
2) ForkJoinPool(JDK 7+)
- 작업을 쪼개서 병렬 처리하는 divide-and-conquer 기반 풀
- 주로 parallelStream(), RecursiveTask 등에 사용됨
| 기존 구조 |
한계 |
| ThreadPoolExecutor |
병렬 분할 작업에 비효율적. 큐가 중앙 집중적 → 병목 발생 |
| Future |
블로킹 기반 → get() 호출 시 점유 자원 낭비 |
| 병렬 작업 직접 스레드 처리 |
재귀적 분할 작업에 비효율적 (예: 머지소트, DFS, 큰 루프 나누기 등) |
3) CompletableFuture(JDK 8+)
4) Virtual Threads(Project Loom, JDK 21+)
내부 구조 및 특성 비교
| 항목 |
Thread |
ExecutorService |
ForkJoinPool |
CompletableFuture |
VirtualThread |
| 도입 시기 |
Java 1.0 |
Java 1.5 |
Java 7 |
Java 8 |
Java 21 |
| 주요 목적 |
기본 동시성 제어 |
작업 분산 및 재사용 |
병렬 분할 정복 |
비동기 논블로킹 |
대규모 동시 처리 |
| 생성 비용 |
높음 (OS 스레드) |
보통 (풀 기반) |
보통 (풀 기반) |
낮음 (논블로킹) |
매우 낮음 (경량) |
| 작업 큐 |
직접 구현 |
공유 큐 (BlockingQueue) |
각 스레드 Deque (Work Stealing) |
없음 (비동기 체인) |
없음 (JVM 스케줄링) |
| 스레드 관리 |
직접 생성/관리 |
스레드 풀 자동 관리 |
워크 스틸링 기반 풀 |
내부 풀 사용 |
JVM Scheduler가 관리 |
| 블로킹 여부 |
블로킹 |
블로킹 |
비블로킹 (권장) |
논블로킹 |
블로킹 가능 (JVM이 처리) |
| 확장성 |
낮음 |
중간 |
높음 (CPU-bound에 적합) |
높음 (조합 가능) |
매우 높음 (수십만 개) |
| 적합한 작업 |
단순 병렬 작업 |
일반 병렬 처리 |
재귀적 병렬 분할 작업 |
비동기 로직 조합 |
대규모 IO 중심 동시성 |
+-------------------------+
| Thread |
+-------------------------+
▲
|
+----------------+----------------+
| |
+---------------------+ +---------------------------+
| Executor (인터페이스) | | ForkJoinPool |
+---------------------+ +---------------------------+
▲ ▲
| |
+---------------------+ +---------------------------+
| ThreadPoolExecutor | | CompletableFuture |
+---------------------+ +---------------------------+
▲ ▲
| |
+-------------+-------------------+
|
+-------------------------------+
| Executors.newVirtualThread... |
+-------------------------------+
- Thread는 모든 동시성 API의 가장 기초가 되는 실행 단위입니다.
- Executor는 작업을 실행하는 전략(예: 스레드풀, 가상 스레드)을 추상화한 인터페이스입니다.
- ThreadPoolExecutor, ForkJoinPool은 Executor의 구현체로 실제 실행 전략을 담당합니다.
- CompletableFuture는 Executor에 태스크를 전달하고 비동기 흐름을 연결하는 고수준 API입니다.
- Java 21 이후에는 Executor에 Virtual Thread 기반 구현체를 주입해 경량 동시성을 구현할 수 있습니다.
사용 예 요약
- Thread: 직접 스레드 생성 필요할 때 (학습/실험)
- ExecutorService: 다수 작업을 효율적으로 처리 (실무 기본)
- ForkJoinPool: 정렬, 탐색, 집계 등 CPU 병렬 처리
- CompletableFuture: API 연동, 이벤트 처리 등 비동기 조합
- VirtualThread: 웹 요청 처리, 대량 IO, 코루틴 구조 대체
결론
- Thread는 Java 병렬 처리의 기초였으나, 이제는 실무에서 직접 사용 빈도는 거의 없음
- 실무에서는 Executors / CompletableFuture / VirtualThread로 추상화 또는 최적화된 처리 사용
- 대체 방식은 성능, 코드 품질, 관리 편의성을 모두 개선하기 위해 도입됨